123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321 |
- 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.
- */
- 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,
- };
- }
- /**
- * 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);
- }
- };
|