Mice in a maze/mice.py

A Mice in a maze interpreter in python, written by User:Koen in 2018.
==About==
This interpreter was tested on the Hello, world! program from Mice_in_a_maze#Example_programs.
===Padding===
The interpreter automatically pads shorter lines with spaces, and adds four extra walls of 'W' around the code, so the mice cannot escape. These extra walls were added for convenience. In particular, I did not bother stripping the '\n' character at the end of each line, so the right outer wall is actually two spaces to the right of the farthest instruction.
===Code flow===
At each step, every mouse executes the instruction it's standing on, then moves if it can. Instructions 'C' and 'A' immediately cause the mouse to rotate, which might or might not be what the language author intended.
A mouse moves immediately after executing its instruction, or doesn't move if it is surrounded by four walls. Rotating does not constitute a move; mice rotate clockwise until they are not facing a wall, then move one step forward.
This timing issue might be relevant if a mouse is standing next to a conditional wall 'K', as this is the only way mice ever interact. If a mouse is currently standing on a 'K', it does not care whether this 'K' is a wall or not; only adjacent walls matter.
Mice are ordered according to their initial position in the maze (top to bottom, left to right). At every step, mice are activated in that order. The digit used to declare the mouse is ignored by the interpreter. In particular, two mice can share the same digit, and it is possible to declare more than 10 mice.
Execution halts when there are no mice left. If a mouse is never destroyed, execution loops.
===Brainfuck memory===
The tape is unbounded to the right. The data pointer starts at cell 0. Executing instruction '<' while at cell 0 has no effect.
Each cell holds a python int and is initialised to 0. 
===Input and output===
Output is flushed after each execution of the instruction '.'. Cells are converted into chars using python function chr.
The input instruction ',' calls the python function sys.stdin.read(1).
==Usage==
Assuming the source code is in file interpreter.py:
python3 interpreter.py maze.txtReads the Mice in a maze code from file maze.txt.
python3 interpreter.pyReads the code from stdin.
==Source code==
import sys
def get_sourcecode(argv):
	if (len(sys.argv) == 1):
		sourcecode = sys.stdin.readlines()
	else:
		with open(sys.argv[1], 'r') as sourcefile:
			sourcecode = sourcefile.readlines()
	return(sourcecode)
def add_padding(code):
	maxlen = max([len(line) for line in code])
	maze = []
	maze.append("\n".rjust(maxlen+2, 'W'))
	for line in code:
		maze.append("W" + line.ljust(maxlen) + "W")
	maze.append("\n".rjust(maxlen+2, 'W'))
	return(maze)
def get_mice(sourcecode):
	mice = []
	for y in range(len(sourcecode)):
		for x in range(len(sourcecode[y])):
			if str.isdigit(sourcecode[y][x]):
				mice.append((x,y,0,-1))
	return(mice)
def facing_wall(x, y, dx, dy, maze, cell_value):
	return( (maze[y+dy][x+dx] == 'W') or (cell_value != 0 and maze[y+dy][x+dx] == 'K') )
def rotate(dx, dy, clockwise = True):
	directions = [(0, -1), (1, 0), (0, 1), (-1, 0)]
	old_i = directions.index((dx, dy))
	new_i = ((old_i+1) % 4) if clockwise else ((old_i-1) % 4)
	return (directions[new_i])
def move_mouse(x, y, dx, dy, maze, cell_value):
	count_rotation = 0
	while (count_rotation < 4 and facing_wall(x, y, dx, dy, maze, cell_value)):
		dx, dy = rotate(dx, dy)
		count_rotation = count_rotation + 1
	if (count_rotation < 4):
		return (x+dx, y+dy, dx, dy)
def execute_instruction(instr, data, ptr, dx, dy):
	destroy_mouse = False
	if (instr == '>'):
		ptr = ptr + 1
		if ptr >= len(data):
			data.extend([0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, 0,0])
	elif (instr == '<'):
		ptr = max(ptr-1, 0)
	elif (instr == '+'):
		data[ptr] = data[ptr] + 1
	elif (instr == '-'):
		data[ptr] = data[ptr] - 1
	elif (instr == '.'):
		sys.stdout.write(chr(data[ptr]))
		sys.stdout.flush()
	elif (instr == ','):
		data[ptr] = sys.stdin.read(1)
	elif (instr == 'E'):
		destroy_mouse = True
	elif (instr == 'C'):
		dx, dy = rotate(dx, dy)
	elif (instr == 'A'):
		dx, dy = rotate(dx, dy, clockwise = False)
	return (ptr, dx, dy, destroy_mouse)
def main():
	code = get_sourcecode(sys.argv)
	maze = add_padding(code)
	data = [0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, 0,0]
	ptr = 0
	mice = get_mice(maze)
	next_round_mice = []
	while (mice):
		for i, (x, y, dx, dy) in enumerate(mice):
			(ptr, dx, dy, destroyed) = execute_instruction(maze[y][x], data, ptr, dx, dy)
			(x, y, dx, dy) = move_mouse(x, y, dx, dy, maze, data[ptr])
			if (not destroyed):
				next_round_mice.append((x, y, dx, dy))
		mice = next_round_mice
		next_round_mice = []
main()
===Comments===
A mouse is a quadruple (x, y, dx, dy), where (x, y) is its position in the maze, and (dx, dy) the cardinal direction it's facing: (0, -1) is up, (1, 0) is right.
mice is the list of mice in the maze, in order. Once a mouse has executed its instruction and moved, it is added to next_round_mice, unless it was destroyed. Execution loops until the list mice is empty.
The tape is a python list called data, initially containing 32 cells, and extended with 32 new cells every time the data pointer is too big.
Category:Implementations