Computer.js 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. const prompt = require("prompt-sync")({ sigint: true });
  2. const util = require("util");
  3. const Stack = require("./Stack");
  4. const ComputerParameterMode = require("./ComputerParameterMode");
  5. const { DeepClone } = require("./common");
  6. module.exports = class Computer {
  7. /**
  8. * An Intcode Computer for the Advent of Code 2019 challenge
  9. *
  10. * @author Apis Necros
  11. *
  12. * @param {number[]} stack The initial memory to load into the computer
  13. * @param {Object} options Options that can be enabled within the computer
  14. * @param {boolean} options.followPointer When true, the memory will be dumped every call to Execute with the current instruction highlighted
  15. */
  16. constructor(stack, options = {}) {
  17. this._initialMemory = DeepClone(stack);
  18. this.stack = new Stack(stack);
  19. this.OPCODES = {
  20. ADD: 1,
  21. MULTIPLY: 2,
  22. INPUT: 3,
  23. OUTPUT: 4,
  24. JUMP_IF_TRUE: 5,
  25. JUMP_IF_FALSE: 6,
  26. LESS_THAN: 7,
  27. EQUALS: 8,
  28. HALT: 99,
  29. };
  30. this.parameterMode = ComputerParameterMode.POSITION_MODE;
  31. /**
  32. * Whether the Execute loop should skip moving the pointer after running the opcode
  33. *
  34. * Some opcodes, such as JUMP_IF_TRUE set the stack pointer, and as such shouldn't have
  35. * the Execute function move it after the opcode finishes executing.
  36. */
  37. this.skipNext = false;
  38. this.options = {
  39. followPointer: options.followPointer ?? false,
  40. };
  41. }
  42. /**
  43. * Run the computer
  44. *
  45. * Runs opcodes on the stack until either the a HALT command is
  46. * encountered, or an error is thrown.
  47. * @returns {void}
  48. */
  49. Run() {
  50. // eslint-disable-next-line no-empty
  51. while (this.Execute(this.stack.Get(ComputerParameterMode.IMMEDIATE_MODE)) === true) { }
  52. }
  53. /**
  54. * Execute a call using the provided opcode
  55. *
  56. * @param {number} rawOpcode A opcode to execute
  57. * @returns {boolean} False if the opcode was HALT, otherwise true
  58. */
  59. Execute(rawOpcode) {
  60. let status = true;
  61. this.skipNext = false;
  62. if (this.options.followPointer) {
  63. this.DumpMemory(true);
  64. }
  65. const opcode = rawOpcode % 100;
  66. // console.log(`DEBUG: opcode: ${opcode}`);
  67. switch (opcode) {
  68. case this.OPCODES.ADD: {
  69. this.Operation_Add(rawOpcode);
  70. break;
  71. }
  72. case this.OPCODES.MULTIPLY: {
  73. this.Operation_Multiply(rawOpcode);
  74. break;
  75. }
  76. case this.OPCODES.INPUT: {
  77. this.Operation_Input();
  78. break;
  79. }
  80. case this.OPCODES.OUTPUT: {
  81. this.Operation_Output();
  82. break;
  83. }
  84. case this.OPCODES.JUMP_IF_TRUE: {
  85. this.Operation_JumpIf(rawOpcode, true);
  86. break;
  87. }
  88. case this.OPCODES.JUMP_IF_FALSE: {
  89. this.Operation_JumpIf(rawOpcode, false);
  90. break;
  91. }
  92. case this.OPCODES.HALT:
  93. status = false;
  94. break;
  95. default:
  96. throw Error(`Opcode ${opcode} not found\nMemdump: ${JSON.stringify(this.stack.Dump())}\nPointer: ${this.stack.pointer}`);
  97. }
  98. if (!this.skipNext) {
  99. this.stack.Next();
  100. }
  101. return status;
  102. }
  103. /**
  104. * Parse operands based on the current parameter mode
  105. *
  106. * When the int computer is in Immediate Mode, the values are returned
  107. * as-is. When in Position Mode, the operands are used as memory
  108. * addresses, and the values at those addresses are returned instead.
  109. *
  110. * @returns {number[]} The parsed list of operands
  111. */
  112. _ParseOperands(...operands) {
  113. if (this.parameterMode == ComputerParameterMode.IMMEDIATE_MODE) { return operands; }
  114. return operands.map((operand) => this.stack.Get(operand));
  115. }
  116. /**
  117. * Execute the Add opcode
  118. *
  119. * Adds two numbers and stores the result at the provided position
  120. * on the stack.
  121. *
  122. * Parses the operand Parameter Mode out of the opcode used to make
  123. * this call.
  124. *
  125. * @param {number} rawOpcode The opcode in memory used to make this call
  126. * @returns {void}
  127. */
  128. Operation_Add(rawOpcode) {
  129. const operandLeftMode = ComputerParameterMode.ParseParameterMode(rawOpcode, 1);
  130. const operandRightMode = ComputerParameterMode.ParseParameterMode(rawOpcode, 2);
  131. const operandLeft = this.stack.Next().Get(operandLeftMode);
  132. const operandRight = this.stack.Next().Get(operandRightMode);
  133. const outputPosition = this.stack.Next().Get(ComputerParameterMode.IMMEDIATE_MODE);
  134. const newValue = operandLeft + operandRight;
  135. this.stack.Put(outputPosition, newValue);
  136. }
  137. /**
  138. * Execute the Multiply opcode
  139. *
  140. * Multiplies two numbers and stores the result at the provided
  141. * position on the stack.
  142. *
  143. * @param {number} rawOpcode The opcode in memory used to make this call
  144. * @returns {void}
  145. */
  146. Operation_Multiply(rawOpcode) {
  147. const operandLeftMode = ComputerParameterMode.ParseParameterMode(rawOpcode, 1);
  148. const operandRightMode = ComputerParameterMode.ParseParameterMode(rawOpcode, 2);
  149. const operandLeft = this.stack.Next().Get(operandLeftMode);
  150. const operandRight = this.stack.Next().Get(operandRightMode);
  151. const outputPosition = this.stack.Next().Get(ComputerParameterMode.IMMEDIATE_MODE);
  152. const newValue = operandLeft * operandRight;
  153. this.stack.Put(outputPosition, newValue);
  154. }
  155. /**
  156. * Execute the Input opcode
  157. *
  158. * Prompts the user to input a value from the command line
  159. *
  160. * @returns {void}
  161. */
  162. Operation_Input() {
  163. const outputPosition = this.stack.Next().Get(ComputerParameterMode.IMMEDIATE_MODE);
  164. let userInput;
  165. do {
  166. userInput = Number(prompt("Please enter a number: "));
  167. } while (Number.isNaN(userInput));
  168. this.stack.Put(outputPosition, userInput);
  169. }
  170. /**
  171. * Execute the OUTPUT opcode
  172. *
  173. * @returns {void}
  174. */
  175. Operation_Output() {
  176. const currAddress = this.stack.pointer;
  177. const outputPosition = this.stack.Next().Get(ComputerParameterMode.IMMEDIATE_MODE);
  178. console.log(`OUTPUT FROM ADDRESS ${currAddress}: ${this.stack.GetAtIndex(outputPosition, ComputerParameterMode.IMMEDIATE_MODE)}`);
  179. }
  180. /**
  181. * Execute the Jump_If_True and Jump_If_False opcodes
  182. *
  183. * Jumps to a given address in memory if the value at next address is memory matches
  184. * the given true/false condition.
  185. *
  186. * @param {number} rawOpcode The opcode in memory used to make this call
  187. * @param {boolean} testCondition The value the memory value should be compared against
  188. * @returns {void}
  189. */
  190. Operation_JumpIf(rawOpcode, testCondition) {
  191. const paramMode = ComputerParameterMode.ParseParameterMode(rawOpcode, 1);
  192. const jumpAddressMode = ComputerParameterMode.ParseParameterMode(rawOpcode, 2);
  193. const param = this.stack.Next().Get(paramMode);
  194. const jumpAddress = this.stack.Next().Get(jumpAddressMode);
  195. const performJump = !!param == testCondition;
  196. if (performJump) {
  197. this.skipNext = true;
  198. this.stack.SetPointerAddress(jumpAddress);
  199. }
  200. }
  201. /**
  202. * Outputs the computer's stack to the console
  203. *
  204. * @param {boolean} [highlightPointer=false] Should the memory address of the current pointer be highlighted
  205. * @returns {void}
  206. */
  207. DumpMemory(highlightPointer = false) {
  208. let memory = this.stack.Dump();
  209. if (highlightPointer) {
  210. memory = memory.map((instruction, idx) => (idx == this.stack.pointer ? `{${instruction}}` : instruction));
  211. }
  212. console.log(util.inspect(memory, { breakLength: Infinity, colors: true, compact: true }));
  213. }
  214. /**
  215. * Resets the computer's memory to the value it was created with
  216. *
  217. * @returns {void}
  218. */
  219. Reset() {
  220. this.stack = new Stack(this._initialMemory);
  221. }
  222. /**
  223. * Sets the computer's memory to a new stack
  224. *
  225. * Note: This resets the computer's initial memory, so `Reset` will use this value
  226. *
  227. * @param {number[]} stack The new memory stack for the computer
  228. * @returns {void}
  229. */
  230. SetMemory(stack) {
  231. this._initialMemory = DeepClone(stack);
  232. this.stack = new Stack(stack);
  233. }
  234. };