0 | 0 |
#!/usr/bin/env python
|
1 | 1 |
|
2 | |
### Nhohnhehr interpreter ###
|
|
2 |
# ### Nhohnhehr interpreter ###
|
3 | 3 |
|
4 | 4 |
# Written by Marinus. The contents of this file are in the public domain.
|
5 | 5 |
# Adapted from this esowiki page on May 10 2011:
|
6 | 6 |
# 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.
|
7 | 9 |
|
8 | 10 |
# usage: nhohnhehr.py bits filename (binary I/O)
|
9 | 11 |
# nhohnhehr.py [bytes] filename (ASCII I/O)
|
10 | 12 |
|
11 | 13 |
import sys
|
12 | 14 |
|
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)
|
15 | 25 |
|
16 | 26 |
|
17 | 27 |
# room
|
18 | 28 |
class Room:
|
19 | 29 |
data = None
|
20 | 30 |
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:
|
24 | 35 |
return self.data[y][x]
|
25 | 36 |
else:
|
26 | 37 |
raise IndexError("value out of range")
|
27 | |
|
|
38 |
|
28 | 39 |
def __str__(self):
|
29 | 40 |
return "\n".join(''.join(str(item) for item in line) for line in self.data)
|
30 | |
|
|
41 |
|
31 | 42 |
# transformations
|
32 | 43 |
NONE, CW, CCW, ROT = range(4)
|
33 | |
|
|
44 |
|
34 | 45 |
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:
|
37 | 49 |
# rotate 180 degrees: flip lines, and reverse each line
|
38 | 50 |
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:
|
41 | 54 |
# clockwise 90 degrees
|
42 | 55 |
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)]
|
44 | 57 |
for y in range(self.size):
|
45 | 58 |
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:
|
48 | 61 |
# counterclockwise 90 degrees
|
49 | 62 |
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)]
|
51 | 64 |
for y in range(self.size):
|
52 | 65 |
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]
|
54 | 67 |
else:
|
55 | |
raise ValueError("invalid transformation (%d)"%transformation)
|
56 | |
|
|
68 |
raise ValueError("invalid transformation (%d)" % transformation)
|
|
69 |
|
57 | 70 |
# init room from file or from room+transformation
|
58 | 71 |
def __init__(self, file=None, room=None, transform=None):
|
59 | 72 |
if (file and room) or (not (file or room)):
|
60 | 73 |
raise TypeError("Room needs to be initialized with either a file or a room.")
|
61 | |
|
|
74 |
|
62 | 75 |
# init from file
|
63 | 76 |
if file:
|
64 | 77 |
self.data = []
|
65 | |
|
|
78 |
|
66 | 79 |
# read file
|
67 | 80 |
lines = file.readlines()
|
68 | |
|
|
81 |
|
69 | 82 |
# find possible top-left coordinates for the box
|
70 | 83 |
possibleStartCoords = []
|
71 | 84 |
for y in range(len(lines)):
|
72 | 85 |
for x in range(len(lines[y])):
|
73 | 86 |
if lines[y][x] == '+':
|
74 | 87 |
try:
|
75 | |
if lines[y][x+1] == '-' and lines[y+1][x]=='|':
|
|
88 |
if lines[y][x + 1] == '-' and lines[y + 1][x] == '|':
|
76 | 89 |
possibleStartCoords.append((x, y))
|
77 | 90 |
except IndexError:
|
78 | 91 |
# we hit a boundary looking for | or -, so this
|
79 | 92 |
# isn't a valid one.
|
80 | 93 |
pass
|
81 | |
|
|
94 |
|
82 | 95 |
# check if a box can be found
|
83 | 96 |
startCoords = None
|
84 | 97 |
roomSize = 0
|
85 | 98 |
for (x, y) in possibleStartCoords:
|
86 | |
|
|
99 |
|
87 | 100 |
line = lines[y]
|
88 | 101 |
# 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):
|
93 | 107 |
# no '+' here.
|
94 | 108 |
continue
|
95 | |
|
|
109 |
|
96 | 110 |
# found
|
97 | |
size = x2 - x
|
|
111 |
size = x2 - x
|
98 | 112 |
ok = False
|
99 | 113 |
# see if it's square
|
100 | 114 |
try:
|
101 | 115 |
# 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) + '+':
|
103 | 117 |
ok = True
|
104 | 118 |
# 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 |
|
109 | 124 |
except IndexError:
|
110 | 125 |
# we went outside of the file, so this one isn't valid
|
111 | 126 |
ok = False
|
112 | |
|
|
127 |
|
113 | 128 |
if not ok:
|
114 | 129 |
# try next pair
|
115 | 130 |
continue
|
|
117 | 132 |
# found one!
|
118 | 133 |
if startCoords:
|
119 | 134 |
# 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)."
|
122 | 137 |
% (startCoords[0], startCoords[1], x, y))
|
123 | 138 |
else:
|
124 | 139 |
# + 1 because that's the start of the data, we don't need the boundary
|
|
126 | 141 |
roomSize = size - 1
|
127 | 142 |
# and we have to continue looking in case we find another one,
|
128 | 143 |
# in which case the file is invalid.
|
129 | |
|
|
144 |
|
130 | 145 |
# no room in the file
|
131 | 146 |
if not startCoords:
|
132 | 147 |
raise ValueError("Cannot find a valid room in this file.")
|
133 | |
|
|
148 |
|
134 | 149 |
# we have a room, load it
|
135 | 150 |
x, y = startCoords
|
136 | 151 |
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 |
|
139 | 154 |
self.size = roomSize
|
140 | 155 |
|
141 | 156 |
# init from other room
|
|
143 | 158 |
# this one's easier
|
144 | 159 |
self.size = room.size
|
145 | 160 |
self.data = [line[:] for line in room.data]
|
146 | |
|
|
161 |
|
147 | 162 |
# transformation needed?
|
148 | 163 |
if transform:
|
149 | 164 |
self.transform(transform)
|
150 | |
|
151 | |
|
152 | |
|
|
165 |
|
|
166 |
|
153 | 167 |
class Environment:
|
154 | 168 |
rooms = {}
|
155 | 169 |
ip = ()
|
|
157 | 171 |
edgemode = None
|
158 | 172 |
roomsize = 0
|
159 | 173 |
halt = False
|
160 | |
|
|
174 |
|
161 | 175 |
# states
|
162 | |
WRAP,COPY,CW,CCW,ROT = range(5)
|
163 | |
|
|
176 |
WRAP, COPY, CW, CCW, ROT = range(5)
|
|
177 |
|
164 | 178 |
# 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):
|
168 | 182 |
# get whatever's in that room at that space
|
|
183 |
(x, y) = v
|
169 | 184 |
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
|
172 | 187 |
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 |
}
|
176 | 194 |
self.roomsize = room.size
|
177 | 195 |
self.dir = Environment.RIGHT
|
178 | 196 |
self.edgemode = Environment.WRAP
|
179 | 197 |
self.infunc, self.outfunc = infunc, outfunc
|
180 | 198 |
self.halt = False
|
181 | 199 |
# find initial instruction pointer
|
182 | |
|
|
200 |
|
183 | 201 |
self.ip = (-1, -1)
|
184 | 202 |
for x in range(self.roomsize):
|
185 | 203 |
for y in range(self.roomsize):
|
186 | 204 |
if room[x, y] == '$':
|
187 | 205 |
self.ip = (x, y)
|
188 | 206 |
break
|
189 | |
|
|
207 |
|
190 | 208 |
if self.ip == (-1, -1):
|
191 | 209 |
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 |
|
196 | 215 |
def advanceIP(self):
|
197 | 216 |
newIP = addvec(self.ip, self.dir)
|
198 | |
|
|
217 |
|
199 | 218 |
if self.roomCoords(self.ip) != self.roomCoords(newIP):
|
200 | 219 |
if self.edgemode == Environment.WRAP:
|
201 | 220 |
# wrap to edge of last room
|
|
204 | 223 |
# make a new room if none exists yet
|
205 | 224 |
if not self.roomCoords(newIP) in self.rooms:
|
206 | 225 |
# 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 |
})
|
215 | 238 |
self.ip = newIP
|
216 | |
|
|
239 |
|
217 | 240 |
def step(self):
|
218 | 241 |
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 |
}
|
223 | 254 |
|
224 | 255 |
if command == '/':
|
225 | 256 |
self.dir = ccwrot[self.dir]
|
226 | 257 |
elif command == '\\':
|
227 | 258 |
self.dir = cwrot[self.dir]
|
228 | 259 |
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]
|
234 | 267 |
elif command == '#':
|
235 | 268 |
self.advanceIP()
|
236 | 269 |
elif command == '?':
|
237 | 270 |
try:
|
238 | |
self.dir = ( self.infunc() and cwrot or ccwrot ) [self.dir]
|
|
271 |
self.dir = (self.infunc() and cwrot or ccwrot)[self.dir]
|
239 | 272 |
except IOError:
|
240 | 273 |
# no more input available = do nothing
|
241 | 274 |
pass
|
|
243 | 276 |
self.outfunc(int(command))
|
244 | 277 |
elif command == '@':
|
245 | 278 |
self.halt = True
|
246 | |
|
|
279 |
|
247 | 280 |
self.advanceIP()
|
248 | |
|
|
281 |
|
249 | 282 |
def run(self):
|
250 | |
while not self.halt: self.step()
|
251 | |
|
|
283 |
while not self.halt:
|
|
284 |
self.step()
|
|
285 |
|
252 | 286 |
|
253 | 287 |
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])
|
266 | 300 |
sys.exit()
|
267 | |
|
268 | |
if len(argv)==2:
|
|
301 |
|
|
302 |
if len(argv) == 2:
|
269 | 303 |
mode = 'bytes'
|
270 | 304 |
fname = argv[1]
|
271 | 305 |
else:
|
272 | 306 |
mode = argv[1]
|
273 | 307 |
fname = argv[2]
|
274 | |
|
|
308 |
|
275 | 309 |
# i/o functions
|
276 | 310 |
def bits_in():
|
277 | 311 |
i = None
|
278 | |
while not i in ('0','1'):
|
|
312 |
while i not in ('0', '1'):
|
279 | 313 |
i = sys.stdin.read(1)
|
280 | 314 |
if i == '':
|
281 | |
raise IOError() # eof
|
|
315 |
raise IOError() # eof
|
282 | 316 |
return int(i)
|
283 | |
|
|
317 |
|
284 | 318 |
def bits_out(bit):
|
285 | 319 |
sys.stdout.write(('0', '1')[bit])
|
286 | 320 |
sys.stdout.flush()
|
287 | |
|
|
321 |
|
288 | 322 |
def bytes_in(bits=[[]]):
|
289 | 323 |
# get data if necessary
|
290 | |
if bits[0]==[]:
|
|
324 |
if bits[0] == []:
|
291 | 325 |
i = sys.stdin.read(1)
|
292 | |
if (i==''): raise IOError() # eof
|
|
326 |
if (i == ''):
|
|
327 |
raise IOError() # eof
|
293 | 328 |
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 |
|
296 | 331 |
# return data
|
297 | 332 |
bit = bits[0][0]
|
298 | 333 |
bits[0] = bits[0][1:]
|
299 | 334 |
return bit
|
300 | |
|
|
335 |
|
301 | 336 |
def bytes_out(bit, bits=[[]]):
|
302 | 337 |
bits[0].append(bit)
|
303 | |
|
|
338 |
|
304 | 339 |
# if we have 8 bits, output
|
305 | 340 |
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))))
|
307 | 342 |
sys.stdout.flush()
|
308 | 343 |
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 |
}
|
312 | 349 |
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)
|