Set and Forget


Fork me on GitHub
2019-12-17

Day 17: Set and Forget

Description:
--- Day 17: Set and Forget ---

An early warning system detects an incoming solar flare and automatically activates the ship's electromagnetic shield. Unfortunately, this has cut off the Wi-Fi for many small robots that, unaware of the impending danger, are now trapped on exterior scaffolding on the unsafe side of the shield. To rescue them, you'll have to act quickly!

The only tools at your disposal are some wired cameras and a small vacuum robot currently asleep at its charging station. The video quality is poor, but the vacuum robot has a needlessly bright LED that makes it easy to spot no matter where it is.

An Intcode program, the Aft Scaffolding Control and Information Interface (ASCII, your puzzle input), provides access to the cameras and the vacuum robot. Currently, because the vacuum robot is asleep, you can only access the cameras.

Running the ASCII program on your Intcode computer will provide the current view of the scaffolds. This is output, purely coincidentally, as ASCII code: 35 means #, 46 means ., 10 starts a new line of output below the current one, and so on. (Within a line, characters are drawn left-to-right.)

In the camera output, # represents a scaffold and . represents open space. The vacuum robot is visible as ^, v, <, or > depending on whether it is facing up, down, left, or right respectively. When drawn like this, the vacuum robot is always on a scaffold; if the vacuum robot ever walks off of a scaffold and begins tumbling through space uncontrollably, it will instead be visible as X.

In general, the scaffold forms a path, but it sometimes loops back onto itself. For example, suppose you can see the following view from the cameras:

..#..........
..#..........
#######...###
#.#...#...#.#
#############
..#...#...#..
..#####...^..

Here, the vacuum robot, ^ is facing up and sitting at one end of the scaffold near the bottom-right of the image. The scaffold continues up, loops across itself several times, and ends at the top-left of the image.

The first step is to calibrate the cameras by getting the alignment parameters of some well-defined points. Locate all scaffold intersections; for each, its alignment parameter is the distance between its left edge and the left edge of the view multiplied by the distance between its top edge and the top edge of the view. Here, the intersections from the above image are marked O:

..#..........
..#..........
##O####...###
#.#...#...#.#
##O###O###O##
..#...#...#..
..#####...^..

For these intersections:

The top-left intersection is 2 units from the left of the image and 2 units from the top of the image, so its alignment parameter is 2 * 2 = 4.
The bottom-left intersection is 2 units from the left and 4 units from the top, so its alignment parameter is 2 * 4 = 8.
The bottom-middle intersection is 6 from the left and 4 from the top, so its alignment parameter is 24.
The bottom-right intersection's alignment parameter is 40.

To calibrate the cameras, you need the sum of the alignment parameters. In the above example, this is 76.

Run your ASCII program. What is the sum of the alignment parameters for the scaffold intersections?

--- Part Two ---

Now for the tricky part: notifying all the other robots about the solar flare. The vacuum robot can do this automatically if it gets into range of a robot. However, you can't see the other robots on the camera, so you need to be thorough instead: you need to make the vacuum robot visit every part of the scaffold at least once.

The vacuum robot normally wanders randomly, but there isn't time for that today. Instead, you can override its movement logic with new rules.

Force the vacuum robot to wake up by changing the value in your ASCII program at address 0 from 1 to 2. When you do this, you will be automatically prompted for the new movement rules that the vacuum robot should use. The ASCII program will use input instructions to receive them, but they need to be provided as ASCII code; end each line of logic with a single newline, ASCII code 10.

First, you will be prompted for the main movement routine. The main routine may only call the movement functions: A, B, or C. Supply the movement functions to use as ASCII text, separating them with commas (,, ASCII code 44), and ending the list with a newline (ASCII code 10). For example, to call A twice, then alternate between B and C three times, provide the string A,A,B,C,B,C,B,C and then a newline.

Then, you will be prompted for each movement function. Movement functions may use L to turn left, R to turn right, or a number to move forward that many units. Movement functions may not call other movement functions. Again, separate the actions with commas and end the list with a newline. For example, to move forward 10 units, turn left, move forward 8 units, turn right, and finally move forward 6 units, provide the string 10,L,8,R,6 and then a newline.

Finally, you will be asked whether you want to see a continuous video feed; provide either y or n and a newline. Enabling the continuous video feed can help you see what's going on, but it also requires a significant amount of processing power, and may even cause your Intcode computer to overheat.

Due to the limited amount of memory in the vacuum robot, the ASCII definitions of the main routine and the movement functions may each contain at most 20 characters, not counting the newline.

For example, consider the following camera feed:

#######...#####
#.....#...#...#
#.....#...#...#
......#...#...#
......#...###.#
......#.....#.#
^########...#.#
......#.#...#.#
......#########
........#...#..
....#########..
....#...#......
....#...#......
....#...#......
....#####......

In order for the vacuum robot to visit every part of the scaffold at least once, one path it could take is:

R,8,R,8,R,4,R,4,R,8,L,6,L,2,R,4,R,4,R,8,R,8,R,8,L,6,L,2

Without the memory limit, you could just supply this whole string to function A and have the main routine call A once. However, you'll need to split it into smaller parts.

One approach is:

Main routine: A,B,C,B,A,C
(ASCII input: 65, 44, 66, 44, 67, 44, 66, 44, 65, 44, 67, 10)
Function A: R,8,R,8
(ASCII input: 82, 44, 56, 44, 82, 44, 56, 10)
Function B: R,4,R,4,R,8
(ASCII input: 82, 44, 52, 44, 82, 44, 52, 44, 82, 44, 56, 10)
Function C: L,6,L,2
(ASCII input: 76, 44, 54, 44, 76, 44, 50, 10)

Visually, this would break the desired path into the following parts:

A, B, C, B, A, C
R,8,R,8, R,4,R,4,R,8, L,6,L,2, R,4,R,4,R,8, R,8,R,8, L,6,L,2

CCCCCCA...BBBBB
C.....A...B...B
C.....A...B...B
......A...B...B
......A...CCC.B
......A.....C.B
^AAAAAAAA...C.B
......A.A...C.B
......AAAAAA#AB
........A...C..
....BBBB#BBBB..
....B...A......
....B...A......
....B...A......
....BBBBA......

Of course, the scaffolding outside your ship is much more complex.

As the vacuum robot finds other robots and notifies them of the impending solar flare, it also can't help but leave them squeaky clean, collecting any space dust it finds. Once it finishes the programmed set of movements, assuming it hasn't drifted off into space, the cleaning robot will return to its docking station and report the amount of space dust it collected as a large, non-ASCII value in a single output instruction.

After visiting every part of the scaffold at least once, how much dust does the vacuum robot report it has collected?

Input:
1,330,331,332,109,3016,1101,1182,0,16,1101,1441,0,24,102,1,0,570,1006,570,36,1002,571,1,0,1001,570,-1,570,1001,24,1,24,1106,0,18,1008,571,0,571,1001,16,1,16,1008,16,1441,570,1006,570,14,21101,58,0,0,1105,1,786,1006,332,62,99,21101,333,0,1,21101,73,0,0,1105,1,579,1101,0,0,572,1101,0,0,573,3,574,101,1,573,573,1007,574,65,570,1005,570,151,107,67,574,570,1005,570,151,1001,574,-64,574,1002,574,-1,574,1001,572,1,572,1007,572,11,570,1006,570,165,101,1182,572,127,102,1,574,0,3,574,101,1,573,573,1008,574,10,570,1005,570,189,1008,574,44,570,1006,570,158,1106,0,81,21101,340,0,1,1106,0,177,21102,1,477,1,1106,0,177,21101,514,0,1,21102,176,1,0,1105,1,579,99,21102,1,184,0,1105,1,579,4,574,104,10,99,1007,573,22,570,1006,570,165,1002,572,1,1182,21101,0,375,1,21101,211,0,0,1105,1,579,21101,1182,11,1,21101,222,0,0,1105,1,979,21102,1,388,1,21102,1,233,0,1105,1,579,21101,1182,22,1,21102,244,1,0,1106,0,979,21102,401,1,1,21101,0,255,0,1106,0,579,21101,1182,33,1,21101,266,0,0,1106,0,979,21102,414,1,1,21101,0,277,0,1105,1,579,3,575,1008,575,89,570,1008,575,121,575,1,575,570,575,3,574,1008,574,10,570,1006,570,291,104,10,21102,1,1182,1,21101,0,313,0,1106,0,622,1005,575,327,1102,1,1,575,21101,327,0,0,1106,0,786,4,438,99,0,1,1,6,77,97,105,110,58,10,33,10,69,120,112,101,99,116,101,100,32,102,117,110,99,116,105,111,110,32,110,97,109,101,32,98,117,116,32,103,111,116,58,32,0,12,70,117,110,99,116,105,111,110,32,65,58,10,12,70,117,110,99,116,105,111,110,32,66,58,10,12,70,117,110,99,116,105,111,110,32,67,58,10,23,67,111,110,116,105,110,117,111,117,115,32,118,105,100,101,111,32,102,101,101,100,63,10,0,37,10,69,120,112,101,99,116,101,100,32,82,44,32,76,44,32,111,114,32,100,105,115,116,97,110,99,101,32,98,117,116,32,103,111,116,58,32,36,10,69,120,112,101,99,116,101,100,32,99,111,109,109,97,32,111,114,32,110,101,119,108,105,110,101,32,98,117,116,32,103,111,116,58,32,43,10,68,101,102,105,110,105,116,105,111,110,115,32,109,97,121,32,98,101,32,97,116,32,109,111,115,116,32,50,48,32,99,104,97,114,97,99,116,101,114,115,33,10,94,62,118,60,0,1,0,-1,-1,0,1,0,0,0,0,0,0,1,14,0,0,109,4,1202,-3,1,587,20102,1,0,-1,22101,1,-3,-3,21102,0,1,-2,2208,-2,-1,570,1005,570,617,2201,-3,-2,609,4,0,21201,-2,1,-2,1105,1,597,109,-4,2105,1,0,109,5,1202,-4,1,630,20101,0,0,-2,22101,1,-4,-4,21102,0,1,-3,2208,-3,-2,570,1005,570,781,2201,-4,-3,652,21002,0,1,-1,1208,-1,-4,570,1005,570,709,1208,-1,-5,570,1005,570,734,1207,-1,0,570,1005,570,759,1206,-1,774,1001,578,562,684,1,0,576,576,1001,578,566,692,1,0,577,577,21101,0,702,0,1106,0,786,21201,-1,-1,-1,1105,1,676,1001,578,1,578,1008,578,4,570,1006,570,724,1001,578,-4,578,21101,0,731,0,1105,1,786,1105,1,774,1001,578,-1,578,1008,578,-1,570,1006,570,749,1001,578,4,578,21101,756,0,0,1106,0,786,1105,1,774,21202,-1,-11,1,22101,1182,1,1,21101,774,0,0,1106,0,622,21201,-3,1,-3,1105,1,640,109,-5,2106,0,0,109,7,1005,575,802,20101,0,576,-6,20101,0,577,-5,1106,0,814,21101,0,0,-1,21101,0,0,-5,21102,1,0,-6,20208,-6,576,-2,208,-5,577,570,22002,570,-2,-2,21202,-5,45,-3,22201,-6,-3,-3,22101,1441,-3,-3,2101,0,-3,843,1005,0,863,21202,-2,42,-4,22101,46,-4,-4,1206,-2,924,21102,1,1,-1,1105,1,924,1205,-2,873,21101,35,0,-4,1106,0,924,1201,-3,0,878,1008,0,1,570,1006,570,916,1001,374,1,374,1202,-3,1,895,1101,2,0,0,1201,-3,0,902,1001,438,0,438,2202,-6,-5,570,1,570,374,570,1,570,438,438,1001,578,558,922,20101,0,0,-4,1006,575,959,204,-4,22101,1,-6,-6,1208,-6,45,570,1006,570,814,104,10,22101,1,-5,-5,1208,-5,35,570,1006,570,810,104,10,1206,-1,974,99,1206,-1,974,1101,0,1,575,21101,973,0,0,1105,1,786,99,109,-7,2106,0,0,109,6,21102,0,1,-4,21102,1,0,-3,203,-2,22101,1,-3,-3,21208,-2,82,-1,1205,-1,1030,21208,-2,76,-1,1205,-1,1037,21207,-2,48,-1,1205,-1,1124,22107,57,-2,-1,1205,-1,1124,21201,-2,-48,-2,1105,1,1041,21102,-4,1,-2,1105,1,1041,21101,-5,0,-2,21201,-4,1,-4,21207,-4,11,-1,1206,-1,1138,2201,-5,-4,1059,1202,-2,1,0,203,-2,22101,1,-3,-3,21207,-2,48,-1,1205,-1,1107,22107,57,-2,-1,1205,-1,1107,21201,-2,-48,-2,2201,-5,-4,1090,20102,10,0,-1,22201,-2,-1,-2,2201,-5,-4,1103,2101,0,-2,0,1106,0,1060,21208,-2,10,-1,1205,-1,1162,21208,-2,44,-1,1206,-1,1131,1106,0,989,21102,439,1,1,1105,1,1150,21102,477,1,1,1106,0,1150,21101,514,0,1,21102,1149,1,0,1105,1,579,99,21101,1157,0,0,1105,1,579,204,-2,104,10,99,21207,-3,22,-1,1206,-1,1138,2101,0,-5,1176,2101,0,-4,0,109,-6,2106,0,0,10,5,40,1,44,1,44,1,44,7,44,1,44,1,7,13,24,1,7,1,11,1,8,7,9,1,7,1,11,1,8,1,5,1,9,1,7,1,11,1,8,1,5,1,9,1,1,5,1,1,5,9,6,1,5,1,9,1,1,1,3,1,1,1,5,1,5,1,1,1,6,1,5,1,7,11,1,11,1,1,6,1,5,1,7,1,1,1,1,1,3,1,3,1,3,1,7,1,6,1,5,1,7,1,1,7,3,1,3,1,7,1,6,1,5,1,7,1,3,1,7,1,3,1,7,1,6,1,5,1,1,11,1,11,7,1,6,1,5,1,1,1,5,1,5,1,5,1,11,1,6,1,5,9,5,1,5,1,11,1,6,1,7,1,11,1,5,1,11,1,6,7,1,1,11,1,5,7,5,7,6,1,1,1,11,1,11,1,11,1,6,1,1,13,11,1,11,1,6,1,25,1,11,1,6,1,25,1,11,1,6,1,25,1,11,1,6,1,25,1,11,1,6,1,25,1,11,1,6,1,25,1,1,11,6,1,25,1,1,1,16,7,19,7,40,1,3,1,40,1,3,1,40,1,3,1,40,5,6

Part 1:
namespace AdventOfCode2019_17_1
{
	const DAY     = 17;
	const PROBLEM = 1;

	export async function run()
	{
		let input = await AdventOfCode.getInput(DAY);
		if (input == null) return;

		const code = input.trim().split(",").map(p => parseInt(p.trim()));

		let rnr = new Interpreter(code, []);

		let map: {[_:number]: number} = {};
		let minx = 0;
		let maxx = 0;
		let miny = 0;
		let maxy = 0;
		let out  = "";
		let x=0;
		let y=0
		while (!rnr.is_halted)
		{
			rnr.singleStep();
			if (rnr.output.length>0) 
			{
				const v = rnr.output.pop()!;
				out += String.fromCharCode(v);
				await AdventOfCode.outputIntermed(out);

				if (v === 10)
				{
					y++;
					x=0;
				}
				else
				{
					AdventOfCode.outputConsole(`# [${x}|${y}] = ${v}`);

					map[y*1_0000 + x] = v;
					x++;

					minx = Math.min(minx, x);
					maxx = Math.max(maxx, x);
					miny = Math.min(miny, y);
					maxy = Math.max(maxy, y);
				}
			}
		}

		let result = 0;

		for(let yy=miny+1;yy<=maxy-1;yy++)
		for(let xx=minx+1;xx<=maxx-1;xx++)
		{
			const v0 = map[(yy  )*1_0000 + (xx  )];
			const vN = map[(yy-1)*1_0000 + (xx  )];
			const vE = map[(yy  )*1_0000 + (xx+1)];
			const vS = map[(yy+1)*1_0000 + (xx  )];
			const vW = map[(yy  )*1_0000 + (xx-1)];

			if (v0 != 35) continue;
			if (vN != 35) continue;
			if (vE != 35) continue;
			if (vS != 35) continue;
			if (vW != 35) continue;

			let vv = xx*yy;

			AdventOfCode.outputConsole(`[${xx}|${yy}] = ${vv}`);

			result += vv;
		}

		AdventOfCode.output(DAY, PROBLEM, result.toString());
	}

	class Interpreter
	{
		program: InfMem;
		inputqueue: number[];
		instructionpointer: number;
		output: number[];
		relative_base: number;

		is_halted: boolean = false;

		constructor(prog: number[], input: number[])
		{
			this.program = new InfMem(prog);
			this.inputqueue = input;
			this.instructionpointer = 0;
			this.output = [];
			this.relative_base = 0;
		}

		fullRun() : number[]
		{
			while(!this.is_halted)
			{
				const r = this.singleStep();

				if (r === StepResult.EXECUTED) continue;
				if (r === StepResult.HALTED) return this.output;
				if (r === StepResult.WAITING_FOR_IN) throw "not enough input";

				throw "unknown output of singleStep";
			}

			return this.output;
		}

		autoRun() : StepResult
		{
			while(!this.is_halted)
			{
				const r = this.singleStep();

				if (r === StepResult.EXECUTED) continue;
				if (r === StepResult.HALTED) return StepResult.HALTED;
				if (r === StepResult.WAITING_FOR_IN) return StepResult.WAITING_FOR_IN;

				throw "unknown output of singleStep";
			}

			return StepResult.HALTED;
		}

		singleStep() : StepResult
		{
			const cmd = new Op(this.program.r(this.instructionpointer));

			if (cmd.opcode == OpCode.ADD)
			{
				const p0 = cmd.getParameter(this, 0);
				const p1 = cmd.getParameter(this, 1);
				const pv = p0 + p1;
				cmd.setParameter(this, 2, pv);

				this.incInstrPtr(cmd);
				
				return StepResult.EXECUTED;
			}
			else if (cmd.opcode == OpCode.MUL)
			{
				const p0 = cmd.getParameter(this, 0);
				const p1 = cmd.getParameter(this, 1);
				const pv = p0 * p1;
				cmd.setParameter(this, 2, pv);

				this.incInstrPtr(cmd);
				
				return StepResult.EXECUTED;
			}
			else if (cmd.opcode == OpCode.HALT)
			{
				this.is_halted = true;
				return StepResult.HALTED;
			}
			else if (cmd.opcode == OpCode.IN)
			{
				if (this.inputqueue.length == 0) return StepResult.WAITING_FOR_IN;

				const pv = this.inputqueue[0];
				cmd.setParameter(this, 0, pv);
				this.inputqueue = this.inputqueue.slice(1);

				this.incInstrPtr(cmd);
				return StepResult.EXECUTED;
			}
			else if (cmd.opcode == OpCode.OUT)
			{
				const p0 = cmd.getParameter(this, 0);
				this.output.push(p0);
				//AdventOfCode.outputConsole("# " + p0);

				this.incInstrPtr(cmd);

				return StepResult.EXECUTED;
			}
			else if (cmd.opcode == OpCode.TJMP)
			{
				const p0 = cmd.getParameter(this, 0);
				if (p0 != 0) this.instructionpointer = cmd.getParameter(this, 1);
				else this.incInstrPtr(cmd);
				
				return StepResult.EXECUTED;
			}
			else if (cmd.opcode == OpCode.FJMP)
			{
				const p0 = cmd.getParameter(this, 0);
				if (p0 == 0) this.instructionpointer = cmd.getParameter(this, 1);
				else this.incInstrPtr(cmd);
				
				return StepResult.EXECUTED;
			}
			else if (cmd.opcode == OpCode.LT)
			{
				const p0 = cmd.getParameter(this, 0);
				const p1 = cmd.getParameter(this, 1);
				const pv = p0 < p1 ? 1 : 0;
				cmd.setParameter(this, 2, pv);

				this.incInstrPtr(cmd);
				return StepResult.EXECUTED;
			}
			else if (cmd.opcode == OpCode.EQ)
			{
				const p0 = cmd.getParameter(this, 0);
				const p1 = cmd.getParameter(this, 1);
				const pv = p0 == p1 ? 1 : 0;
				cmd.setParameter(this, 2, pv);

				this.incInstrPtr(cmd);
				return StepResult.EXECUTED;
			}
			else if (cmd.opcode == OpCode.ARB)
			{
				const p0 = cmd.getParameter(this, 0);
				this.relative_base = this.relative_base+p0;

				this.incInstrPtr(cmd);
				return StepResult.EXECUTED;
			}
			else throw "Unknown Op: " + cmd.opcode + " @ " + this.instructionpointer;
		}

		private incInstrPtr(cmd: Op)
		{
			this.instructionpointer += 1 + cmd.parametercount;
		}
	}

	enum StepResult { EXECUTED, HALTED, WAITING_FOR_IN }

	enum OpCode 
	{
		ADD  = 1,
		MUL  = 2,
		IN   = 3,
		OUT  = 4,
		TJMP = 5,
		FJMP = 6,
		LT   = 7,
		EQ   = 8,
		ARB  = 9,
		HALT = 99,
	}

	enum ParamMode
	{
		POSITION_MODE  = 0,
		IMMEDIATE_MODE = 1,
		RELATIVE_MODE  = 2,
	}

	class Op
	{
		opcode: OpCode;
		modes: ParamMode[];

		name: string;
		parametercount: number;

		constructor(v: number)
		{
			this.opcode = v%100;
			v = Math.floor(v/100);
			this.modes = [];
			for(let i=0; i<4; i++) 
			{
				this.modes.push(v%10);
				v = Math.floor(v/10);
			}

			     if (this.opcode == OpCode.ADD)  { this.name="ADD";   this.parametercount=3; }
			else if (this.opcode == OpCode.MUL)  { this.name="MUL";   this.parametercount=3; }
			else if (this.opcode == OpCode.HALT) { this.name="HALT";  this.parametercount=0; }
			else if (this.opcode == OpCode.IN)   { this.name="IN";    this.parametercount=1; }
			else if (this.opcode == OpCode.OUT)  { this.name="OUT";   this.parametercount=1; }
			else if (this.opcode == OpCode.TJMP) { this.name="TJMP";  this.parametercount=2; }
			else if (this.opcode == OpCode.FJMP) { this.name="FJMP";  this.parametercount=2; }
			else if (this.opcode == OpCode.LT)   { this.name="LT";    this.parametercount=3; }
			else if (this.opcode == OpCode.EQ)   { this.name="EQ";    this.parametercount=3; }
			else if (this.opcode == OpCode.ARB)  { this.name="ARB";   this.parametercount=1; }
			else throw "Unknown opcode: "+this.opcode;
		}

		getParameter(proc: Interpreter, index: number): number
		{
			const prog = proc.program;
			const ip   = proc.instructionpointer;

			let p = prog.r(ip+1+index);

				 if (this.modes[index] == ParamMode.POSITION_MODE)  p = prog.r(p);
			else if (this.modes[index] == ParamMode.IMMEDIATE_MODE) p = p;
			else if (this.modes[index] == ParamMode.RELATIVE_MODE)  p = prog.r(proc.relative_base+p);
			else throw "Unknown ParamMode: "+this.modes[index];

			return p;
		}

		setParameter(proc: Interpreter, index: number, value: number): void
		{
			const prog = proc.program;
			const ip   = proc.instructionpointer;

			let p = prog.r(ip+1+index);

				 if (this.modes[index] == ParamMode.POSITION_MODE)  prog.w(p, value);
			else if (this.modes[index] == ParamMode.IMMEDIATE_MODE) throw "Immediate mode not allowed in write";
			else if (this.modes[index] == ParamMode.RELATIVE_MODE)  prog.w(proc.relative_base+p, value);
			else throw "Unknown ParamMode: "+this.modes[index];
		}
	}

	class InfMem
	{
		private data: { [_:number]:number } = {};   

		constructor(v: number[])
		{
			for(let i=0; i<v.length;i++) this.data[i]=v[i];
		}

		r(pos: number): number
		{
			if (!(pos in this.data)) this.data[pos] = 0;
			return this.data[pos];
		}

		w(pos: number, val: number): number
		{
			return this.data[pos] = val;
		}
	}
}


Result: 3448

Part 2:
namespace AdventOfCode2019_17_2
{
	const DAY     = 17;
	const PROBLEM = 2;

	export async function run()
	{
		let input = await AdventOfCode.getInput(DAY);
		if (input == null) return;

		const code = input.trim().split(",").map(p => parseInt(p.trim()));

		let [map, maxx, maxy] = await getMap(code);

		const fullpath = getFullPath(map, maxx, maxy);

		let proginput = await splitPath(fullpath, "y");

		code[0]=2;
		let rnr = new Interpreter(code, proginput);
		while(!rnr.is_halted && (rnr.output.length < 0 || rnr.output[0] !== "?".charCodeAt(0))) { rnr.output=[]; rnr.singleStep(); }
		while(!rnr.is_halted && (rnr.output.length < 0 || rnr.output[0] !== ".".charCodeAt(0))) { rnr.output=[]; rnr.singleStep(); }
		while(!rnr.is_halted)
		{
			rnr.singleStep();

			if (rnr.output.length === (maxx+1)*(maxy+1)+1)
			{
				let str = rnr.output.map(p => String.fromCharCode(p)).reduce((a,b)=>a+b);
				await AdventOfCode.outputIntermed(str);
				await AdventOfCode.sleepIfIntermed(10);
				rnr.output = [];

				AdventOfCode.outputConsole(str);
				AdventOfCode.outputConsole("---------------NEXT------------");
			}
		}

		AdventOfCode.outputConsole(rnr.output.map(p => String.fromCharCode(p)).reduce((a,b)=>a+b));


		AdventOfCode.output(DAY, PROBLEM, rnr.output[0].toString());
	}

	async function splitPath(fullpath: RoboCommand[], live: string): Promise<number[]>
	{
		let allsplits: { [pos:number]: [ number, RoboCommand[], number[] ][] } = {};
		for(let i=0; i<fullpath.length; i++) allsplits[i] = [];

		let splitmap: { [pos:number]: [ RoboCommand[], number[] ] } = {};

		let splitid = 10000;
		for(let splitlen = 10; splitlen>0;splitlen--)
		{
			let localfounds: Set<number> = new Set<number>();
			for (let start=0; start <= fullpath.length-splitlen; start++)
			{
				if (localfounds.has(start)) continue;

				let occurences = findInPath(fullpath, start, splitlen);
				if (occurences.length === 1) continue;

				for(const occ of occurences) localfounds.add(occ);

				let localsplit = fullpath.slice(start, start+splitlen);

				let localsplitstr = localsplit.map(p=>p.toString()).reduce((a,b)=>a+","+b);
				if  (localsplitstr.length>20) continue;

				let r: [ number, RoboCommand[], number[] ] = [ splitid++, localsplit, occurences ];

				splitmap[r[0]] = [ r[1], r[2] ];

				AdventOfCode.outputConsole(`||(${r[0]}) ${occurences.length} x [${localsplitstr}]  -->  [${occurences.map(p=>p.toString()).reduce((a,b)=>a+";"+b)}]`);
				
				for(const occ of occurences) allsplits[occ].push(r);
			}
		}

		// ------------

		let sf = new Splitfinder();
		sf.allsplits = allsplits;
		sf.fulllength = fullpath.length;

		sf.run();

		let result = "";
		let intresult = [];

		let progmap: {[_:number]: string} = {};
		let first = true;
		let rc0: RoboCommand[] = [];
		let rc1: RoboCommand[] = [];
		let rc2: RoboCommand[] = [];
		for(const comp of sf.results[0])
		{
			let id = "";
			if (comp in progmap) id = progmap[comp];
			else
			{
				if (Object.entries(progmap).length==0)      { id="A"; rc0 = splitmap[comp][0]; }
				else if (Object.entries(progmap).length==1) { id="B"; rc1 = splitmap[comp][0]; }
				else if (Object.entries(progmap).length==2) { id="C"; rc2 = splitmap[comp][0]; }
				progmap[comp] = id;
			}

			if (!first) { result += ","; intresult.push(44); }
			result += id;
			intresult.push(id.charCodeAt(0));
			first = false;
		}

		result += "\n";
		intresult.push(10);

		let str0 = rc0.map(p => p.toString()).reduce((a,b)=>a+","+b);
		result += str0+"\n";
		str0.split('').forEach(p => intresult.push(p.charCodeAt(0)));
		intresult.push(10);

		let str1 = rc1.map(p => p.toString()).reduce((a,b)=>a+","+b);
		result += str1+"\n";
		str1.split('').forEach(p => intresult.push(p.charCodeAt(0)));
		intresult.push(10);

		let str2 = rc2.map(p => p.toString()).reduce((a,b)=>a+","+b);
		result += str2+"\n";
		str2.split('').forEach(p => intresult.push(p.charCodeAt(0)));
		intresult.push(10);

		result += live + "\n";
		intresult.push(live.charCodeAt(0));
		intresult.push(10);

		AdventOfCode.outputConsole(result);

		return intresult;
	}

	class Splitfinder
	{
		allsplits: { [pos:number]: [ number, RoboCommand[], number[] ][] } = {}; // pos => [ id, path, starts ]
		fulllength: number = 0;

		results: number[][] = [];

		path: number[] = [];
		used_segments: [number, number][] = [];
		position = 0;

		run()
		{
			// abort if we finished
			if (this.position === this.fulllength) 
			{
				this.results.push(Object.assign([], this.path));
				AdventOfCode.outputConsole(">>> " + this.path.map(p => "["+p.toString()+"]").reduce((a,b)=>a+","+b) )
				return;
			}

			for(const seg of this.allsplits[this.position])
			{
				// ======== add to used_segments ========
				let added = false;
				for(let i=0; i<this.used_segments.length; i++) 
				{
					if (this.used_segments[i][0] === seg[0])
					{
						this.used_segments[i][1]++;
						added = true;
						break;
					}
				}
				if (!added)
				{
					if (this.used_segments.length === 3) continue;

					this.used_segments.push([seg[0], 1]);
				}

				// ======== add to path ========
				this.path.push(seg[0]);

				// ======== inc pos ========
				this.position += seg[1].length;

				// ======== recursion ========
				this.run();

				// ======== dec pos ========
				this.position -= seg[1].length;

				// ======== rem from path ========
				this.path.pop();

				// ======== rem from used_segments ========
				let rem_us = false;
				for(let i=0; i<this.used_segments.length; i++) 
				{
					if (this.used_segments[i][0] === seg[0])
					{
						this.used_segments[i][1]--;
						if (this.used_segments[i][1] === 0) rem_us = true;
						break;
					}
				}
				if (rem_us)
				{
					this.used_segments = this.used_segments.filter(p => p[1] > 0);
				}
			}
		}
	}

	function findInPath(fullpath: RoboCommand[], start: number, len: number): number[]
	{
		let r = [start];
		for (let s = start+len; s<=fullpath.length-len;s++)
		{
			if (isPathEqual(fullpath, start, s, len)) r.push(s);
		}
		return r;
	}

	function isPathEqual(path: RoboCommand[], start1: number, start2: number, len: number)
	{
		for (let i=0; i<len; i++)
		{
			if (!(path[start1+i].eq(path[start2+i]))) return false;
		}
		return true;
	}

	function getFullPath(map: {[_:number]: number}, maxx: number, maxy: number)
	{
		let robox = -1;
		let roboy = -1;
		let robod = Direction.North;

		for(let yy=0;yy<=maxy;yy++)
		for(let xx=0;xx<=maxx;xx++)
		{
			let id = (yy*1_0000 + xx);
			if (map[id] === 94)  { robox=xx; roboy=yy; robod=Direction.North; break; }
			if (map[id] === 62)  { robox=xx; roboy=yy; robod=Direction.East;  break; }
			if (map[id] === 60)  { robox=xx; roboy=yy; robod=Direction.West;  break; }
			if (map[id] === 118) { robox=xx; roboy=yy; robod=Direction.South; break; }
		}

		let result: RoboCommand[] = [];
		let pos: [number, number] = [robox, roboy];
		let dir = robod;

		let moves = 0;
		for(;;)
		{
			if (isScaffolding(map, moveDir(pos, dir)))
			{
				moves++;
				pos = moveDir(pos, dir);
			}
			else if (isScaffolding(map, moveDir(pos, turnLeft(dir))))
			{
				if (moves > 0) result.push(new RoboCommand(RoboCommandType.Move, moves));
				moves = 0;
				result.push(new RoboCommand(RoboCommandType.Left, 1));

				dir = turnLeft(dir);
			}
			else if (isScaffolding(map, moveDir(pos, turnRight(dir))))
			{
				if (moves > 0) result.push(new RoboCommand(RoboCommandType.Move, moves));
				moves = 0;
				result.push(new RoboCommand(RoboCommandType.Right, 1));

				dir = turnRight(dir);
			}
			else
			{
				if (moves > 0) result.push(new RoboCommand(RoboCommandType.Move, moves));

				break;
			}
		}

		AdventOfCode.outputConsole(result.map(p => p.toString()).reduce((a,b)=> a+","+b));

		return result;
	}

	function moveDir(pos: [number, number], dir: Direction): [number, number]
	{
		return move(pos, dirToVec(dir))
	}

	function move(pos: [number, number], vec: [number, number]): [number, number]
	{
		return [ pos[0]+vec[0], pos[1]+vec[1] ];
	}

	function isScaffolding(map: {[_:number]: number}, pos: [number, number])
	{
		let id = (pos[1]*1_0000 + pos[0]);
		return map[id] === 35;
	}

	class RoboCommand
	{
		cmd: RoboCommandType = RoboCommandType.Move;
		len: number = 0;

		constructor(c: RoboCommandType, l: number)
		{
			this.cmd = c;
			this.len = l;
		}

		eq(o: RoboCommand) 
		{
			if (this.cmd !== o.cmd) return false;
			if (this.len !== o.len) return false;
			return true;
		}

		toString(): string
		{
			if (this.cmd === RoboCommandType.Left) return "L";
			if (this.cmd === RoboCommandType.Right) return "R";
			if (this.cmd === RoboCommandType.Move) return ""+this.len;

			throw "??";
		}
	}

	enum RoboCommandType { Left, Right, Move }

	enum Direction
	{
		North = 1,
		South = 2,
		West  = 3,
		East  = 4,
	}

	function dirToVec(d: Direction): [number, number]
	{
		if (d === Direction.North) return [0, -1];
		if (d === Direction.East)  return [+1, 0];
		if (d === Direction.South) return [0, +1];
		if (d === Direction.West)  return [-1, 0];

		throw "-.-";
	}

	function turnRight(d: Direction): Direction
	{
		if (d === Direction.North) return Direction.East;
		if (d === Direction.East)  return Direction.South;
		if (d === Direction.South) return Direction.West;
		if (d === Direction.West)  return Direction.North;

		throw "dr";
	}

	function turnLeft(d: Direction): Direction
	{
		if (d === Direction.North) return Direction.West;
		if (d === Direction.East)  return Direction.North;
		if (d === Direction.South) return Direction.East;
		if (d === Direction.West)  return Direction.South;

		throw "dl";
	}

	async function getMap(code: number[]): Promise<[{[_:number]: number}, number, number]>
	{
		let rnr = new Interpreter(code, []);

		let map: {[_:number]: number} = {};
		let maxx = 0;
		let maxy = 0;
		let out  = "";
		let ipx=0;
		let ipy=0
		while (!rnr.is_halted)
		{
			rnr.singleStep();
			if (rnr.output.length>0) 
			{
				const v = rnr.output.pop()!;
				out += String.fromCharCode(v);
				//await AdventOfCode.outputIntermed(out);

				if (v === 10)
				{
					ipy++;
					ipx=0;
				}
				else
				{
					// AdventOfCode.outputConsole(`# [${ipx}|${ipy}] = ${v}`);

					map[ipy*1_0000 + ipx] = v;
					ipx++;

					maxx = Math.max(maxx, ipx);
					maxy = Math.max(maxy, ipy);
				}
			}
		}

		await AdventOfCode.outputIntermed(out);

		for(let yy=-1;yy<=maxy+1;yy++)
		for(let xx=-1;xx<=maxx+1;xx++)
		{
			if (!((yy*1_0000 + xx) in map)) map[yy*1_0000 + xx] = 46;
		}

		return [map, maxx, maxy];
	}

	class Interpreter
	{
		program: InfMem;
		inputqueue: number[];
		instructionpointer: number;
		output: number[];
		relative_base: number;

		is_halted: boolean = false;

		constructor(prog: number[], input: number[])
		{
			this.program = new InfMem(prog);
			this.inputqueue = input;
			this.instructionpointer = 0;
			this.output = [];
			this.relative_base = 0;
		}

		fullRun() : number[]
		{
			while(!this.is_halted)
			{
				const r = this.singleStep();

				if (r === StepResult.EXECUTED) continue;
				if (r === StepResult.HALTED) return this.output;
				if (r === StepResult.WAITING_FOR_IN) throw "not enough input";

				throw "unknown output of singleStep";
			}

			return this.output;
		}

		autoRun() : StepResult
		{
			while(!this.is_halted)
			{
				const r = this.singleStep();

				if (r === StepResult.EXECUTED) continue;
				if (r === StepResult.HALTED) return StepResult.HALTED;
				if (r === StepResult.WAITING_FOR_IN) return StepResult.WAITING_FOR_IN;

				throw "unknown output of singleStep";
			}

			return StepResult.HALTED;
		}

		singleStep() : StepResult
		{
			const cmd = new Op(this.program.r(this.instructionpointer));

			if (cmd.opcode == OpCode.ADD)
			{
				const p0 = cmd.getParameter(this, 0);
				const p1 = cmd.getParameter(this, 1);
				const pv = p0 + p1;
				cmd.setParameter(this, 2, pv);

				this.incInstrPtr(cmd);
				
				return StepResult.EXECUTED;
			}
			else if (cmd.opcode == OpCode.MUL)
			{
				const p0 = cmd.getParameter(this, 0);
				const p1 = cmd.getParameter(this, 1);
				const pv = p0 * p1;
				cmd.setParameter(this, 2, pv);

				this.incInstrPtr(cmd);
				
				return StepResult.EXECUTED;
			}
			else if (cmd.opcode == OpCode.HALT)
			{
				this.is_halted = true;
				return StepResult.HALTED;
			}
			else if (cmd.opcode == OpCode.IN)
			{
				if (this.inputqueue.length == 0) return StepResult.WAITING_FOR_IN;

				const pv = this.inputqueue[0];
				cmd.setParameter(this, 0, pv);
				this.inputqueue = this.inputqueue.slice(1);

				this.incInstrPtr(cmd);
				return StepResult.EXECUTED;
			}
			else if (cmd.opcode == OpCode.OUT)
			{
				const p0 = cmd.getParameter(this, 0);
				this.output.push(p0);
				//AdventOfCode.outputConsole("# " + p0);

				this.incInstrPtr(cmd);

				return StepResult.EXECUTED;
			}
			else if (cmd.opcode == OpCode.TJMP)
			{
				const p0 = cmd.getParameter(this, 0);
				if (p0 != 0) this.instructionpointer = cmd.getParameter(this, 1);
				else this.incInstrPtr(cmd);
				
				return StepResult.EXECUTED;
			}
			else if (cmd.opcode == OpCode.FJMP)
			{
				const p0 = cmd.getParameter(this, 0);
				if (p0 == 0) this.instructionpointer = cmd.getParameter(this, 1);
				else this.incInstrPtr(cmd);
				
				return StepResult.EXECUTED;
			}
			else if (cmd.opcode == OpCode.LT)
			{
				const p0 = cmd.getParameter(this, 0);
				const p1 = cmd.getParameter(this, 1);
				const pv = p0 < p1 ? 1 : 0;
				cmd.setParameter(this, 2, pv);

				this.incInstrPtr(cmd);
				return StepResult.EXECUTED;
			}
			else if (cmd.opcode == OpCode.EQ)
			{
				const p0 = cmd.getParameter(this, 0);
				const p1 = cmd.getParameter(this, 1);
				const pv = p0 == p1 ? 1 : 0;
				cmd.setParameter(this, 2, pv);

				this.incInstrPtr(cmd);
				return StepResult.EXECUTED;
			}
			else if (cmd.opcode == OpCode.ARB)
			{
				const p0 = cmd.getParameter(this, 0);
				this.relative_base = this.relative_base+p0;

				this.incInstrPtr(cmd);
				return StepResult.EXECUTED;
			}
			else throw "Unknown Op: " + cmd.opcode + " @ " + this.instructionpointer;
		}

		private incInstrPtr(cmd: Op)
		{
			this.instructionpointer += 1 + cmd.parametercount;
		}
	}

	enum StepResult { EXECUTED, HALTED, WAITING_FOR_IN }

	enum OpCode 
	{
		ADD  = 1,
		MUL  = 2,
		IN   = 3,
		OUT  = 4,
		TJMP = 5,
		FJMP = 6,
		LT   = 7,
		EQ   = 8,
		ARB  = 9,
		HALT = 99,
	}

	enum ParamMode
	{
		POSITION_MODE  = 0,
		IMMEDIATE_MODE = 1,
		RELATIVE_MODE  = 2,
	}

	class Op
	{
		opcode: OpCode;
		modes: ParamMode[];

		name: string;
		parametercount: number;

		constructor(v: number)
		{
			this.opcode = v%100;
			v = Math.floor(v/100);
			this.modes = [];
			for(let i=0; i<4; i++) 
			{
				this.modes.push(v%10);
				v = Math.floor(v/10);
			}

			     if (this.opcode == OpCode.ADD)  { this.name="ADD";   this.parametercount=3; }
			else if (this.opcode == OpCode.MUL)  { this.name="MUL";   this.parametercount=3; }
			else if (this.opcode == OpCode.HALT) { this.name="HALT";  this.parametercount=0; }
			else if (this.opcode == OpCode.IN)   { this.name="IN";    this.parametercount=1; }
			else if (this.opcode == OpCode.OUT)  { this.name="OUT";   this.parametercount=1; }
			else if (this.opcode == OpCode.TJMP) { this.name="TJMP";  this.parametercount=2; }
			else if (this.opcode == OpCode.FJMP) { this.name="FJMP";  this.parametercount=2; }
			else if (this.opcode == OpCode.LT)   { this.name="LT";    this.parametercount=3; }
			else if (this.opcode == OpCode.EQ)   { this.name="EQ";    this.parametercount=3; }
			else if (this.opcode == OpCode.ARB)  { this.name="ARB";   this.parametercount=1; }
			else throw "Unknown opcode: "+this.opcode;
		}

		getParameter(proc: Interpreter, index: number): number
		{
			const prog = proc.program;
			const ip   = proc.instructionpointer;

			let p = prog.r(ip+1+index);

				 if (this.modes[index] == ParamMode.POSITION_MODE)  p = prog.r(p);
			else if (this.modes[index] == ParamMode.IMMEDIATE_MODE) p = p;
			else if (this.modes[index] == ParamMode.RELATIVE_MODE)  p = prog.r(proc.relative_base+p);
			else throw "Unknown ParamMode: "+this.modes[index];

			return p;
		}

		setParameter(proc: Interpreter, index: number, value: number): void
		{
			const prog = proc.program;
			const ip   = proc.instructionpointer;

			let p = prog.r(ip+1+index);

				 if (this.modes[index] == ParamMode.POSITION_MODE)  prog.w(p, value);
			else if (this.modes[index] == ParamMode.IMMEDIATE_MODE) throw "Immediate mode not allowed in write";
			else if (this.modes[index] == ParamMode.RELATIVE_MODE)  prog.w(proc.relative_base+p, value);
			else throw "Unknown ParamMode: "+this.modes[index];
		}
	}

	class InfMem
	{
		private data: { [_:number]:number } = {};   

		constructor(v: number[])
		{
			for(let i=0; i<v.length;i++) this.data[i]=v[i];
		}

		r(pos: number): number
		{
			if (!(pos in this.data)) this.data[pos] = 0;
			return this.data[pos];
		}

		w(pos: number, val: number): number
		{
			return this.data[pos] = val;
		}
	}
}


Result: 762405


made with vanilla PHP and MySQL, no frameworks, no bootstrap, no unnecessary* javascript