jsadvent/solutions/2019/_intcode.js
2020-12-10 02:33:00 -06:00

314 lines
7.2 KiB
JavaScript

const { Glib } = require('../../lib');
const DEBUG = false;
const OPCODES = {
ADD: 1n,
MULTIPLY: 2n,
INPUT: 3n,
OUTPUT: 4n,
JUMP_IF_TRUE: 5n,
JUMP_IF_FALSE: 6n,
LESS_THAN: 7n,
EQUALS: 8n,
HALT: 99n,
};
const MODES = {
POSITION: 0n,
IMMEDIATE: 1n,
};
const CONDITIONS = {
HALT: Symbol('HALT'),
};
class AbstractParameter {
read() {
throw new Error("can't read");
}
write() {
throw new Error("can't write");
}
position() {
throw new Error("can't write");
}
}
class PositionParameter extends AbstractParameter {
constructor(value) {
super();
this._v = value;
}
read(memory) {
return memory[this._v];
}
write(memory, value) {
return memory.safeSplice(Number(this._v), 1, value);
}
position() {
return this._v;
}
}
class DirectParameter extends AbstractParameter {
constructor(value) {
super();
this._v = value;
}
read() {
return this._v;
}
position() {
return -1n;
}
}
const INSTRUCTIONS = new Map([
[
OPCODES.ADD,
{
arity: 3n,
execute: ({ memory, parameters: [left, right, output] }) => {
DEBUG &&
console.log(
`ADD (${left.position()})->${left.read(
memory,
)} (${right.position()})->${right.read(
memory,
)} TO (${output.position()})`,
);
return {
memory: output.write(memory, left.read(memory) + right.read(memory)),
};
},
},
],
[
OPCODES.MULTIPLY,
{
arity: 3n,
execute: ({ memory, parameters: [left, right, output] }) => {
DEBUG &&
console.log(
`MULTIPLY (${left.position()})->${left.read(
memory,
)} (${right.position()})->${right.read(
memory,
)} TO (${output.position()})`,
);
return {
memory: output.write(memory, left.read(memory) * right.read(memory)),
};
},
},
],
[
OPCODES.INPUT,
{
arity: 1n,
execute: ({ memory, parameters: [target], inputs }) => {
DEBUG && console.log(`INPUT TO (${target.position()})`);
const [input, ...newInputs] = inputs;
return {
memory: target.write(memory, input),
inputs: newInputs,
};
},
},
],
[
OPCODES.OUTPUT,
{
arity: 1n,
execute: ({ memory, parameters: [value], outputs }) => {
DEBUG &&
console.log(`OUTPUT (${value.position()})->${value.read(memory)}`);
return {
outputs: outputs.safePush(value.read(memory)),
};
},
},
],
[
OPCODES.JUMP_IF_TRUE,
{
arity: 2n,
execute: ({ memory, parameters: [conditional, target] }) => {
DEBUG &&
console.log(
`JUMP_IF_TRUE (${conditional.position()})->${conditional.read(
memory,
)} TO (${target.position()})->${target.read(memory)}`,
);
const diff = {};
if (conditional.read(memory) !== 0n) {
diff.instructionPointer = target.read(memory);
}
return diff;
},
},
],
[
OPCODES.JUMP_IF_FALSE,
{
arity: 2n,
execute: ({ memory, parameters: [conditional, target] }) => {
DEBUG &&
console.log(
`JUMP_IF_FALSE (${conditional.position()})->${conditional.read(
memory,
)} TO (${target.position()})->${target.read(memory)}`,
);
const diff = {};
if (conditional.read(memory) === 0n) {
diff.instructionPointer = target.read(memory);
}
return diff;
},
},
],
[
OPCODES.LESS_THAN,
{
arity: 3n,
execute: ({ memory, parameters: [left, right, output] }) => {
DEBUG &&
console.log(
`LESS_THAN (${left.position()})->${left.read(
memory,
)} < (${right.position()})->${right.read(
memory,
)} TO (${output.position()})`,
);
return {
memory: output.write(
memory,
left.read(memory) < right.read(memory) ? 1n : 0n,
),
};
},
},
],
[
OPCODES.EQUALS,
{
arity: 3n,
execute: ({ memory, parameters: [left, right, output] }) => {
DEBUG &&
console.log(
`EQUALS (${left.position()})->${left.read(
memory,
)} === (${right.position()})->${right.read(
memory,
)} TO (${output.position()})`,
);
return {
memory: output.write(
memory,
left.read(memory) === right.read(memory) ? 1n : 0n,
),
};
},
},
],
// JUMP_IF_TRUE: 5n,
// JUMP_IF_FALSE: 6n,
// LESS_THAN: 7n,
// EQUALS: 8n,
[
OPCODES.HALT,
{
arity: 0n,
execute: () => {
DEBUG && console.log(`HALT`);
return {
condition: CONDITIONS.HALT,
};
},
},
],
]);
const PARAMETER_MODES = new Map([
[MODES.POSITION, (value) => new PositionParameter(value)],
[MODES.IMMEDIATE, (value) => new DirectParameter(value)],
]);
const parseInstruction = (instruction) => {
const opcode = instruction % 100n;
const modes = [];
let modeInt = instruction / 100n;
while (modeInt > 0) {
modes.push(modeInt % 10n);
modeInt /= 10n;
}
return {
opcode,
modes,
};
};
const parseParameters = (modes, rawParameters) => {
if (modes.length > rawParameters.length) {
throw new Error('invalid mode arity');
} else if (modes.length < rawParameters.length) {
modes = modes.concat(Array(rawParameters.length - modes.length).fill(0n));
}
return rawParameters.map((value, i) => {
const mode = modes[i];
if (!PARAMETER_MODES.has(mode)) {
throw new Error(`No mode for '${mode}'`);
}
return PARAMETER_MODES.get(mode)(value);
});
};
module.exports = {
parse(text) {
return Glib.fromSplit(text, ',').toInts().array;
},
run(program, inputs = []) {
let state = {
memory: program.slice(),
instructionPointer: 0n,
inputs,
outputs: [],
};
while (state.condition !== CONDITIONS.HALT) {
DEBUG && console.log(state);
const { opcode, modes } = parseInstruction(
state.memory[state.instructionPointer],
);
if (!INSTRUCTIONS.has(opcode)) {
throw new Error(`No instruction for opcode '${opcode}'`);
}
const instruction = INSTRUCTIONS.get(opcode);
const instructionParametersPc = state.instructionPointer + 1n;
const instructionEndPc = instructionParametersPc + instruction.arity;
const parameters = parseParameters(
modes,
instruction.arity > 0n
? state.memory.slice(
Number(instructionParametersPc),
Number(instructionEndPc),
)
: [],
);
if (BigInt(parameters.length) !== instruction.arity) {
throw new Error('Invalid number of parameters');
}
const stateChanges = instruction.execute({ ...state, parameters });
if (!stateChanges.hasOwnProperty('instructionPointer')) {
stateChanges.instructionPointer = instructionEndPc;
}
state = { ...state, ...stateChanges };
}
DEBUG && console.log(state);
return state;
},
};