Computer.js 9.0 KB

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