class Computer {
constructor(inputs = [0], instrs = [99]) {
this.Status = {
Running: AOC.type("Running", []),
AwaitingInput: AOC.type("AwaitingInput", []),
Halted: AOC.type("Halted", [])
};
this.Mode = {
Position: AOC.type("Position", []),
Immediate: AOC.type("Immediate", []),
Relative: AOC.type("Relative", [])
};
this.Opcode = {
Add: (a, b, c) => AOC.type("Add", [a, b, c]),
Mult: (a, b, c) => AOC.type("Mult", [a, b, c]),
Input: (a) => AOC.type("Input", [a]),
Output: (a) => AOC.type("Output", [a]),
JmpIfTrue: (a, b) => AOC.type("JmpIfTrue", [a, b]),
JmpIfFalse: (a, b) => AOC.type("JmpIfFalse", [a, b]),
LessThan: (a, b, c) => AOC.type("LessThan", [a, b, c]),
Equals: (a, b, c) => AOC.type("Equals", [a, b, c]),
ShiftBase: (a) => AOC.type("ShiftBase", [a]),
Halt: AOC.type("Halt", []),
NoOp: AOC.type("NoOp", [])
};
this.mem = instrs.map(AOC.identity);
this.outputStore = [];
this.inputStore = inputs.map(AOC.identity);
this.instrPtr = 0;
this.relativeBase = 0;
this.out = 0;
this.status = this.Status.Running;
}
// -------------------------- Memory
read(addr, mode = this.Mode.Immediate) {
return mode.case({
Immediate: () => (this.mem[addr] === undefined ? 0 : this.mem[addr]),
Position: () =>
this.read(this.mem[addr] === undefined ? 0 : this.mem[addr]),
Relative: () =>
this.read(
this.relativeBase +
(this.mem[addr] === undefined ? 0 : this.mem[addr])
)
});
}
poke(addr, val) {
this.mem[addr] = val;
return this;
}
// -------------------------- Adding input
addInput(input) {
this.inputStore.unshift(input);
return this;
}
addInputs(inputs) {
this.inputStore.unshift(...inputs);
return this;
}
// -------------------------- Operations
readOp(addr) {
const toMode = (modeNum) =>
modeNum === "1"
? this.Mode.Immediate
: modeNum === "2"
? this.Mode.Relative
: this.Mode.Position;
// OpCode is in the rightmost two digits, the mode of each parameter stored in the other digits.
const [modes, opcode] = [
("" + Math.floor(this.read(addr) / 100)).split("").map(toMode).reverse(),
this.read(addr) % 100
];
const readParam = this.read(
addr + 1,
modes.length > 0 ? modes[0] : this.Mode.Position
);
const readAddressModeParam = (a, md) => {
const offset = md === this.Mode.Relative ? this.relativeBase : 0;
return offset + this.read(addr + a);
};
const readAddressParam = (a) =>
readAddressModeParam(a, modes.length > 0 ? modes[0] : this.Mode.Position);
const read3Params = [
this.read(addr + 1, modes.length > 0 ? modes[0] : this.Mode.Position),
this.read(addr + 2, modes.length > 1 ? modes[1] : this.Mode.Position),
readAddressModeParam(3, modes.length > 2 ? modes[2] : this.Mode.Position)
];
switch (opcode) {
case 1:
return this.Opcode.Add(...read3Params);
case 2:
return this.Opcode.Mult(...read3Params);
case 3:
return this.Opcode.Input(readAddressParam(1));
case 4:
return this.Opcode.Output(readParam);
case 5:
return this.Opcode.JmpIfTrue(...read3Params);
case 6:
return this.Opcode.JmpIfFalse(...read3Params);
case 7:
return this.Opcode.LessThan(...read3Params);
case 8:
return this.Opcode.Equals(...read3Params);
case 9:
return this.Opcode.ShiftBase(readParam);
case 99:
return this.Opcode.Halt;
default:
return this.Opcode.NoOp;
}
}
// -------------------------- Execution
run() {
let addr = this.instrPtr;
while (
this.status === this.Status.Running ||
(this.status === this.Status.AwaitingInput && this.inputStore.length > 0)
) {
this.readOp(addr).case({
Add: (p1, p2, p3) => {
this.instrPtr = addr + 4;
this.mem[p3] = p1 + p2;
addr += 4;
},
Mult: (p1, p2, p3) => {
this.instrPtr = addr + 4;
this.mem[p3] = p1 * p2;
addr += 4;
},
Input: (p1) => {
if (this.inputStore.length > 0) {
this.mem[p1] = this.inputStore.shift();
this.status = this.Status.Running;
} else {
this.status = this.Status.AwaitingInput;
}
addr += 2;
},
Output: (p1) => {
this.out = p1;
this.outputStore.push(p1);
this.instrPtr = addr + 2;
addr += 2;
},
JmpIfTrue: (p1, p2, p3) => {
const newAddr = p1 === 0 ? addr + 3 : p2;
this.instrPtr = newAddr;
addr = this.instrPtr;
},
JmpIfFalse: (p1, p2, p3) => {
const newAddr = p1 === 0 ? p2 : addr + 3;
this.instrPtr = newAddr;
addr = this.instrPtr;
},
LessThan: (p1, p2, p3) => {
this.instrPtr = addr + 4;
this.mem[p3] = p1 < p2 ? 1 : 0;
addr += 4;
},
Equals: (p1, p2, p3) => {
this.instrPtr = addr + 4;
this.mem[p3] = p1 === p2 ? 1 : 0;
addr += 4;
},
ShiftBase: (p1) => {
this.instrPtr = addr + 2;
this.relativeBase = this.relativeBase + p1;
addr += 2;
},
Halt: () => {
this.status = this.Status.Halted;
},
NoOp: () => {
this.status = this.Status.Halted;
}
});
}
return this;
}
}