Computer.js 7.6 KB

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