const Stack = require("./Stack"); const ComputerParameterMode = require("./ComputerParameterMode"); const { DeepClone } = require("./common"); /** * An Intcode Computer for the Advent of Code 2019 challenge * * @author Apis Necros */ module.exports = class Computer { constructor(stack) { this._initialMemory = DeepClone(stack); this.stack = new Stack(stack); this.OPCODES = { ADD: 1, MULTIPLY: 2, OUTPUT: 4, HALT: 99, }; this.parameterMode = ComputerParameterMode.POSITION_MODE; } /** * Run the computer * * Runs opcodes on the stack until either the a HALT command is * encountered, or an error is thrown. * @returns {void} */ Run() { // eslint-disable-next-line no-empty while (this.Execute(this.stack.Get()) === true) { } } /** * Execute a call using the provided opcode * * @param {number} opcode A opcode to execute * @returns {boolean} False if the opcode was HALT, otherwise true */ Execute(opcode) { // console.log(`DEBUG: opcode: ${opcode}`) let status = true; switch (opcode) { case this.OPCODES.ADD: { const operandLeft = this.stack.Next().Get(); const operandRight = this.stack.Next().Get(); const position = this.stack.Next().Get(); this.Operation_Add(operandLeft, operandRight, position); break; } case this.OPCODES.MULTIPLY: { const operandLeft = this.stack.Next().Get(); const operandRight = this.stack.Next().Get(); const position = this.stack.Next().Get(); this.Operation_Multiply(operandLeft, operandRight, position); break; } case this.OPCODES.OUTPUT: { const outputPosition = this.stack.Next().Get(); this.Operation_Output(outputPosition); break; } case this.OPCODES.HALT: status = false; break; default: throw Error(`Opcode ${opcode} not found`); } this.stack.Next(); return status; } /** * Parse operands based on the current parameter mode * * When the int computer is in Immediate Mode, the values are returned * as-is. When in Position Mode, the operands are used as memory * addresses, and the values at those addresses are returned instead. * * @returns {number[]} The parsed list of operands */ _ParseOperands(...operands) { if (this.parameterMode == ComputerParameterMode.IMMEDIATE_MODE) { return operands; } return operands.map((operand) => this.stack.Get(operand)); } /** * Execute the Add opcode * * Adds two numbers and stores the result at the provided position * on the stack. * * @param {number} operandLeft The first operand * @param {number} operandRight The second operand * @param {number} outputPosition The position on the stack to place the result * @returns {void} */ Operation_Add(operandLeft, operandRight, outputPosition) { [operandLeft, operandRight] = this._ParseOperands(operandLeft, operandRight); const newValue = operandLeft + operandRight; this.stack.Put(outputPosition, newValue); } /** * Execute the Multiply opcode * * Multiplies two numbers and stores the result at the provided * position on the stack. * * @param {number} operandLeft The first operand * @param {number} operandRight The second operand * @param {number} outputPosition The position on the stack to place the result * @returns {void} */ Operation_Multiply(operandLeft, operandRight, outputPosition) { [operandLeft, operandRight] = this._ParseOperands(operandLeft, operandRight); const newValue = operandLeft * operandRight; this.stack.Put(outputPosition, newValue); } /** * Execute the OUTPUT opcode * * @param {number} outputPosition The memory address of the value to output * @returns {void} */ Operation_Output(outputPosition) { console.log(this.stack.Get(outputPosition)); } /** * Outputs the computer's stack to the console * @returns {void} */ DumpMemory() { console.log(this.stack.Dump()); } /** * Resets the computer's memory to the value it was created with * * @returns {void} */ Reset() { this.stack = new Stack(this._initialMemory); } /** * Sets the computer's memory to a new stack * * Note: This resets the computer's initial memory, so `Reset` will use this value * * @param {number[]} stack The new memory stack for the computer * @returns {void} */ SetMemory(stack) { this._initialMemory = DeepClone(stack); this.stack = new Stack(stack); } }; /** * Check whether the value at a specific spot in a number is non-zero * * Similar to the bitwise & operator, checks a "place" in a decimal number * to see if it has a non-zero value. * * @example * const test = 107; * console.log(DecimalPlaceIsNonZero(test, 1)) // true * console.log(DecimalPlaceIsNonZero(test, 2)) // false * console.log(DecimalPlaceIsNonZero(test, 3)) // true * * @param {number} input The number to check * @param {number} place The power of 10 to check against * @returns {boolean} Whether the value in that number's place is non-zero */ function DecimalPlaceIsNonZero(input, place) { return !!Math.floor((input % (10 ** place)) / (10 ** (place - 1))); }