Computer.js 8.2 KB

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