const prompt = require("prompt-sync")({ sigint: true }); 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, INPUT: 3, 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(ComputerParameterMode.IMMEDIATE_MODE)) === true) { } } /** * Execute a call using the provided opcode * * @param {number} rawOpcode A opcode to execute * @returns {boolean} False if the opcode was HALT, otherwise true */ Execute(rawOpcode) { let status = true; const opcode = rawOpcode % 100; // console.log(`DEBUG: opcode: ${opcode}`); switch (opcode) { case this.OPCODES.ADD: { this.Operation_Add(rawOpcode); break; } case this.OPCODES.MULTIPLY: { this.Operation_Multiply(rawOpcode); break; } case this.OPCODES.INPUT: { this.Operation_Input(); break; } case this.OPCODES.OUTPUT: { this.Operation_Output(); break; } case this.OPCODES.HALT: status = false; break; default: throw Error(`Opcode ${opcode} not found\nMemdump: ${JSON.stringify(this.stack.Dump())}`); } 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. * * Parses the operand Parameter Mode out of the opcode used to make * this call. * * @param {number} rawOpcode The opcode in memory used to make this call * @returns {void} */ Operation_Add(rawOpcode) { const operandLeftMode = ComputerParameterMode.ParseParameterMode(rawOpcode, 1); const operandRightMode = ComputerParameterMode.ParseParameterMode(rawOpcode, 2); const operandLeft = this.stack.Next().Get(operandLeftMode); const operandRight = this.stack.Next().Get(operandRightMode); const outputPosition = this.stack.Next().Get(ComputerParameterMode.IMMEDIATE_MODE); 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} rawOpcode The opcode in memory used to make this call * @returns {void} */ Operation_Multiply(rawOpcode) { const operandLeftMode = ComputerParameterMode.ParseParameterMode(rawOpcode, 1); const operandRightMode = ComputerParameterMode.ParseParameterMode(rawOpcode, 2); const operandLeft = this.stack.Next().Get(operandLeftMode); const operandRight = this.stack.Next().Get(operandRightMode); const outputPosition = this.stack.Next().Get(ComputerParameterMode.IMMEDIATE_MODE); const newValue = operandLeft * operandRight; this.stack.Put(outputPosition, newValue); } /** * Execute the Input opcode * * Prompts the user to input a value from the command line * * @returns {void} */ Operation_Input() { const outputPosition = this.stack.Next().Get(ComputerParameterMode.IMMEDIATE_MODE); let userInput; do { userInput = Number(prompt("Please enter a number: ")); } while (Number.isNaN(userInput)); this.stack.Put(outputPosition, userInput); } /** * Execute the OUTPUT opcode * * @returns {void} */ Operation_Output() { const outputPosition = this.stack.Next().Get(ComputerParameterMode.IMMEDIATE_MODE); console.log(this.stack.GetAtIndex(outputPosition, ComputerParameterMode.IMMEDIATE_MODE)); } /** * 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); } };