123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325 |
- const prompt = require("prompt-sync")({ sigint: true });
- const util = require("util");
- const Stack = require("./Stack");
- const ComputerParameterMode = require("./ComputerParameterMode");
- const { DeepClone } = require("./common");
- module.exports = class Computer {
- /**
- * An Intcode Computer for the Advent of Code 2019 challenge
- *
- * @author Apis Necros
- *
- * @param {number[]} stack The initial memory to load into the computer
- * @param {Object} options Options that can be enabled within the computer
- * @param {boolean} options.followPointer When true, the memory will be dumped every call to Execute with the current instruction highlighted
- * @param {number} options.tickRate The number of milliseconds between calls to Execute. Initializes to 0.
- * @param {boolean} options.inputFromConsole When true, the computer will prompt for input on the console. If false, it will check for an linked computer and, if one exists, will wait for input from that computer.
- * @param {boolean} options.outputToConsole When true, the computer will print the output of opcode 4 to the console. If false, it will check for an linked computer and, if one exists, pass the output to that computer.
- */
- constructor(stack, options = {}) {
- this._initialMemory = DeepClone(stack);
- this.stack = new Stack(stack);
- this.OPCODES = {
- ADD: 1,
- MULTIPLY: 2,
- INPUT: 3,
- OUTPUT: 4,
- JUMP_IF_TRUE: 5,
- JUMP_IF_FALSE: 6,
- LESS_THAN: 7,
- EQUALS: 8,
- HALT: 99,
- };
- this.EQUALITY = {
- EQUALS: 0,
- LESS_THAN: 1,
- };
- this.parameterMode = ComputerParameterMode.POSITION_MODE;
- /**
- * Whether the Execute loop should skip moving the pointer after running the opcode
- *
- * Some opcodes, such as JUMP_IF_TRUE set the stack pointer, and as such shouldn't have
- * the Execute function move it after the opcode finishes executing.
- */
- this.skipNext = false;
- this.options = {
- followPointer: options.followPointer ?? false,
- tickRate: options.tickRate ?? 0,
- inputFromConsole: options.inputFromConsole ?? false,
- outputToConsole: options.outputToConsole ?? false,
- };
- }
- /**
- * Run the computer
- *
- * Runs opcodes on the stack until either the a HALT command is
- * encountered, or an error is thrown.
- * @returns {void}
- */
- async Run() {
- while (this.Execute(this.stack.Get(ComputerParameterMode.IMMEDIATE_MODE)) === true) {
- if (this.options.tickRate) {
- // Sleep
- // eslint-disable-next-line no-await-in-loop, no-promise-executor-return, arrow-parens
- await new Promise(resolve => setTimeout(resolve, this.options.tickRate));
- }
- }
- }
- /**
- * 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;
- this.skipNext = false;
- if (this.options.followPointer) {
- this.DumpMemory(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(rawOpcode);
- break;
- }
- case this.OPCODES.JUMP_IF_TRUE: {
- this.Operation_JumpIf(rawOpcode, true);
- break;
- }
- case this.OPCODES.JUMP_IF_FALSE: {
- this.Operation_JumpIf(rawOpcode, false);
- break;
- }
- case this.OPCODES.LESS_THAN: {
- this.Operation_Equality(rawOpcode, this.EQUALITY.LESS_THAN);
- break;
- }
- case this.OPCODES.EQUALS: {
- this.Operation_Equality(rawOpcode, this.EQUALITY.EQUALS);
- break;
- }
- case this.OPCODES.HALT:
- status = false;
- break;
- default:
- throw Error(`Opcode ${opcode} not found\nMemdump: ${JSON.stringify(this.stack.Dump())}\nPointer: ${this.stack.pointer}`);
- }
- if (!this.skipNext) {
- 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
- *
- * @param {number} rawOpcode The opcode in memory used to make this call
- * @returns {void}
- */
- Operation_Output(rawOpcode) {
- const currAddress = this.stack.pointer;
- const outputPositionMode = ComputerParameterMode.ParseParameterMode(rawOpcode, 1);
- const output = this.stack.Next().Get(outputPositionMode);
- console.log(`OUTPUT FROM ADDRESS ${currAddress}: ${output}`);
- }
- /**
- * Execute the Jump_If_True and Jump_If_False opcodes
- *
- * Jumps to a given address in memory if the value at next address is memory matches
- * the given true/false condition.
- *
- * @param {number} rawOpcode The opcode in memory used to make this call
- * @param {boolean} testCondition The value the memory value should be compared against
- * @returns {void}
- */
- Operation_JumpIf(rawOpcode, testCondition) {
- const paramMode = ComputerParameterMode.ParseParameterMode(rawOpcode, 1);
- const jumpAddressMode = ComputerParameterMode.ParseParameterMode(rawOpcode, 2);
- const param = this.stack.Next().Get(paramMode);
- const jumpAddress = this.stack.Next().Get(jumpAddressMode);
- const performJump = !!param == testCondition;
- if (performJump) {
- this.skipNext = true;
- this.stack.SetPointerAddress(jumpAddress);
- }
- }
- /**
- * Execute the various equality checking opcodes
- *
- * @param {number} rawOpcode The opcode in memory used to make this call
- * @param {number} testCondition The type of equality check to perform as defined in the computer's constructor
- * @returns {void}
- */
- Operation_Equality(rawOpcode, testCondition) {
- 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);
- let testPassed = false;
- switch (testCondition) {
- case this.EQUALITY.EQUALS:
- testPassed = operandLeft == operandRight;
- break;
- case this.EQUALITY.LESS_THAN:
- testPassed = operandLeft < operandRight;
- break;
- default:
- break;
- }
- this.stack.Put(outputPosition, Number(testPassed));
- }
- /**
- * Outputs the computer's stack to the console
- *
- * @param {boolean} [highlightPointer=false] Should the memory address of the current pointer be highlighted
- * @returns {void}
- */
- DumpMemory(highlightPointer = false) {
- let memory = this.stack.Dump();
- if (highlightPointer) {
- memory = memory.map((instruction, idx) => (idx == this.stack.pointer ? `{${instruction}}` : instruction));
- }
- console.log(util.inspect(memory, { breakLength: Infinity, colors: true, compact: true }));
- }
- /**
- * 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);
- }
- };
|