starfish.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. /**
  2. * The code box class
  3. */
  4. class CodeBox {
  5. constructor(codeBoxID, initialStackID, outputID) {
  6. /**
  7. * Possible vectors the pointer can move in
  8. * @type {Object}
  9. */
  10. this.directions = {
  11. NORTH: [ 0, -1],
  12. EAST: [ 1, 0],
  13. SOUTH: [ 0, 1],
  14. WEST: [-1, 0],
  15. };
  16. /**
  17. * The current vector of the pointer
  18. * @type {int[]}
  19. */
  20. this.curr_direction = this.directions.EAST;
  21. /**
  22. * The Set of instructions to execute
  23. *
  24. * Either a 1 or 2-dimensional array
  25. * @type {Array|Array[]}
  26. */
  27. this.box = [];
  28. /**
  29. * The coordinates of the currently executing instruction inside the code box
  30. * @type {int[]}
  31. */
  32. this.pointer = [0,0];
  33. /**
  34. * Was the instruction last moving in the left direction
  35. *
  36. * Used by the {@link Fisherman}
  37. * @type {boolean}
  38. */
  39. this.dirWasLeft = false;
  40. /**
  41. * Are we currently under the influence of the {@link Fisherman}
  42. * @type {boolean}
  43. */
  44. this.onTheHook = false;
  45. /**
  46. * Are we currently processing code box instructions as a string
  47. *
  48. * 0 when false, otherwise it holds the char code for string delimiter, either
  49. * 34 or 39
  50. *
  51. * @type {int}
  52. */
  53. this.stringMode = 0;
  54. /**
  55. * The stack for the code box to work with
  56. *
  57. * @TODO Implement multiple stacks
  58. *
  59. * @type {Stack}
  60. */
  61. this.stack = new Stack();
  62. this.codeBoxDOM = document.getElementById(codeBoxID);
  63. if(!this.codeBoxDOM) {
  64. throw new Error(`Failed to find textarea with ID: ${codeBoxID}`);
  65. }
  66. this.outputDOM = document.getElementById(outputID);
  67. if(!this.outputDOM) {
  68. throw new Error(`Failed to find textarea with ID: ${outputID}`);
  69. }
  70. this.initialStackDOM = document.getElementById(initialStackID);
  71. if(!this.initialStackDOM) {
  72. throw new Error(`Failed to find input with ID: ${initialStackID}`);
  73. }
  74. }
  75. /**
  76. * Parse the initial code box
  77. *
  78. * Transforms the textual code box into usable matrix
  79. */
  80. ParseCodeBox() {
  81. // Reset some field for a clean run
  82. this.box = [];
  83. this.pointer = [0, 0];
  84. this.outputDOM.value = "";
  85. const cbRaw = this.codeBoxDOM.value;
  86. const rows = cbRaw.split("\n").filter((r) => r.length);
  87. let maxRowLength = 0;
  88. for(const row of rows) {
  89. const rowSplit = row.split("");
  90. // Store this for later processing
  91. while(rowSplit.length > maxRowLength) {
  92. maxRowLength = rowSplit.length
  93. }
  94. this.box.push(rowSplit);
  95. }
  96. this.EqualizeBoxWidth(maxRowLength);
  97. let fin = null;
  98. try {
  99. while(!fin) {
  100. fin = this.Swim();
  101. }
  102. }
  103. catch(e) {
  104. console.error(e);
  105. }
  106. }
  107. /**
  108. * Make all the rows in the code box the same length
  109. *
  110. * All rows not long enough will have NOPs added until they're uniform in size.
  111. *
  112. * @param {int} [rowLength] The longest row in the code box
  113. */
  114. EqualizeBoxWidth(rowLength = null) {
  115. if(!rowLength) {
  116. for(const row of this.box) {
  117. if(row.length > rowLength) {
  118. rowLength = row.length;
  119. }
  120. }
  121. }
  122. for(const row of this.box) {
  123. while(row.length < rowLength) {
  124. row.push(" ");
  125. }
  126. }
  127. }
  128. /**
  129. * Print the value to the display
  130. *
  131. * @TODO Set up an actual display
  132. * @param {*} value
  133. */
  134. Output(value) {
  135. this.outputDOM.value += value;
  136. }
  137. Execute(instruction) {
  138. let output = null;
  139. switch(instruction) {
  140. // NOP
  141. case " ":
  142. break;
  143. // Numbers
  144. case "1":
  145. case "2":
  146. case "3":
  147. case "4":
  148. case "5":
  149. case "6":
  150. case "7":
  151. case "8":
  152. case "9":
  153. case "0":
  154. case "a":
  155. case "b":
  156. case "c":
  157. case "d":
  158. case "e":
  159. case "f":
  160. this.stack.Push(parseInt(instruction, 16));
  161. break;
  162. // Operators
  163. case "+": {
  164. const x = this.stack.Pop();
  165. const y = this.stack.Pop();
  166. this.stack.Push(y + x);
  167. break;
  168. }
  169. case "-": {
  170. const x = this.stack.Pop();
  171. const y = this.stack.Pop();
  172. this.stack.Push(y - x);
  173. break;
  174. }
  175. case "*": {
  176. const x = this.stack.Pop();
  177. const y = this.stack.Pop();
  178. this.stack.Push(y * x);
  179. break;
  180. }
  181. case ",": {
  182. const x = this.stack.Pop();
  183. const y = this.stack.Pop();
  184. this.stack.Push(y / x);
  185. break;
  186. }
  187. case "%": {
  188. const x = this.stack.Pop();
  189. const y = this.stack.Pop();
  190. this.stack.Push(y % x);
  191. break;
  192. }
  193. // Output
  194. case "n":
  195. output = this.stack.Pop();
  196. break;
  197. case "o":
  198. output = String.fromCharCode(this.stack.Pop());
  199. break;
  200. // End execution
  201. case ";":
  202. output = true;
  203. break;
  204. default:
  205. throw new Error("Something's fishy!");
  206. }
  207. return output;
  208. }
  209. Swim() {
  210. const instruction = this.box[this.pointer[0]][this.pointer[1]];
  211. if(this.stringMode != 0 && instruction != this.stringMode) {
  212. this.stack.Push(dec(instruction));
  213. }
  214. else {
  215. const exeResult = this.Execute(instruction);
  216. if(exeResult === true) {
  217. return true;
  218. }
  219. else if(exeResult != null) {
  220. this.Output(exeResult);
  221. }
  222. }
  223. this.Move();
  224. }
  225. Move() {
  226. const newX = this.pointer[0] + this.curr_direction[0];
  227. const newY = this.pointer[1] + this.curr_direction[1];
  228. this.SetPointer(newX, newY);
  229. }
  230. /**
  231. * Implement C and .
  232. */
  233. SetPointer(x, y) {
  234. this.pointer = [x, y];
  235. }
  236. /**
  237. * Implement ^
  238. *
  239. * Changes the swim direction upward
  240. */
  241. MoveUp() {
  242. this.curr_direction = this.directions.NORTH;
  243. }
  244. /**
  245. * Implement >
  246. *
  247. * Changes the swim direction rightward
  248. */
  249. MoveRight() {
  250. this.curr_direction = this.directions.EAST;
  251. this.dirWasLeft = false;
  252. }
  253. /**
  254. * Implement v
  255. *
  256. * Changes the swim direction downward
  257. */
  258. MoveDown() {
  259. this.curr_direction = this.directions.SOUTH;
  260. }
  261. /**
  262. * Implement <
  263. *
  264. * Changes the swim direction leftward
  265. */
  266. MoveLeft() {
  267. this.curr_direction = this.directions.WEST;
  268. this.dirWasLeft = true;
  269. }
  270. /**
  271. * Implement /
  272. *
  273. * Reflects the swim direction depending on its starting value
  274. */
  275. ReflectForward() {
  276. if (this.curr_direction == this.directions.NORTH) {
  277. this.MoveRight();
  278. }
  279. else if (this.curr_direction == this.directions.EAST) {
  280. this.MoveUp();
  281. }
  282. else if (this.curr_direction == this.directions.SOUTH) {
  283. this.MoveLeft();
  284. }
  285. else {
  286. this.MoveDown();
  287. }
  288. }
  289. /**
  290. * Implement \
  291. *
  292. * Reflects the swim direction depending on its starting value
  293. */
  294. ReflectBack() {
  295. if (this.curr_direction == this.directions.NORTH) {
  296. this.MoveLeft();
  297. }
  298. else if (this.curr_direction == this.directions.EAST) {
  299. this.MoveDown();
  300. }
  301. else if (this.curr_direction == this.directions.SOUTH) {
  302. this.MoveRight();
  303. }
  304. else {
  305. this.MoveUp();
  306. }
  307. }
  308. /**
  309. * Implement |
  310. *
  311. * Swaps the horizontal swim direction to its opposite
  312. */
  313. HorizontalMirror() {
  314. if (this.curr_direction == this.directions.EAST) {
  315. this.MoveLeft();
  316. }
  317. else {
  318. this.MoveRight();
  319. }
  320. }
  321. /**
  322. * Implement _
  323. *
  324. * Swaps the horizontal swim direction to its opposite
  325. */
  326. VerticalMirror() {
  327. if (this.curr_direction == this.directions.NORTH) {
  328. this.MoveDown();
  329. }
  330. else {
  331. this.MoveUp();
  332. }
  333. }
  334. /**
  335. * Implement #
  336. *
  337. * A combination of the vertical and the horizontal mirror
  338. */
  339. OmniMirror() {
  340. if (this.curr_direction[0]) {
  341. this.VerticalMirror();
  342. }
  343. else {
  344. this.HorizontalMirror();
  345. }
  346. }
  347. /**
  348. * Implement x
  349. *
  350. * Pseudo-randomly switches the swim direction
  351. */
  352. ShuffleDirection() {
  353. this.curr_direction = Object.values(this.directions)[Math.floor(Math.random() * 4)];
  354. }
  355. /**
  356. * Implement `
  357. *
  358. * Changes the swim direction based on the previous direction
  359. * @see https://esolangs.org/wiki/Starfish#Fisherman
  360. */
  361. Fisherman() {
  362. if (this.curr_direction[0]) {
  363. if (this.dirWasLeft) {
  364. this.MoveLeft();
  365. }
  366. else {
  367. this.MoveRight();
  368. }
  369. }
  370. else {
  371. if (this.onTheHook) {
  372. this.onTheHook = false;
  373. this.MoveUp();
  374. }
  375. else {
  376. this.onTheHook = true;
  377. this.MoveDown();
  378. }
  379. }
  380. }
  381. }
  382. /**
  383. * The stack class
  384. */
  385. class Stack {
  386. constructor() {
  387. /**
  388. * The stack
  389. * @type {int[]}
  390. */
  391. this.stack = [];
  392. /**
  393. * A single value saved off the stack
  394. * @type {int}
  395. */
  396. this.register = null;
  397. }
  398. /**
  399. * Wrapper function for Array.prototype.push
  400. * @param {*} newValue
  401. */
  402. Push(newValue) {
  403. this.stack.push(newValue);
  404. }
  405. /**
  406. * Wrapper function for Array.prototype.pop
  407. * @returns {*}
  408. */
  409. Pop() {
  410. return this.stack.pop();
  411. }
  412. /**
  413. * Implement }
  414. *
  415. * Shifts the entire stack leftward by one value
  416. */
  417. ShiftLeft() {
  418. const temp = this.stack.shift();
  419. this.stack.push(temp);
  420. }
  421. /**
  422. * Implement {
  423. *
  424. * Shifts the entire stack rightward by one value
  425. */
  426. ShiftRight() {
  427. const temp = this.stack.pop();
  428. this.stack.unshift(temp);
  429. }
  430. /**
  431. * Implement $
  432. *
  433. * Swaps the top two values of the stack
  434. */
  435. SwapTwo() {
  436. // TODO
  437. }
  438. /**
  439. * Implement :
  440. *
  441. * Duplicates the element on the top of the stack
  442. */
  443. Duplicate() {
  444. this.stack.push(this.stack[this.stack.length-1]);
  445. }
  446. /**
  447. * Implements ~
  448. *
  449. * Removes the element on the top of the stack
  450. */
  451. Remove() {
  452. this.stack.pop();
  453. }
  454. /**
  455. * Implement r
  456. *
  457. * Reverses the entire stack
  458. */
  459. Reverse() {
  460. this.stack.reverse();
  461. }
  462. /**
  463. * Implement l
  464. *
  465. * Pushes the length of the stack onto the top of the stack
  466. */
  467. PushLength() {
  468. this.stack.push(this.stack.length);
  469. }
  470. }
  471. /**
  472. * Get the char code of any character
  473. *
  474. * Can actually take any length of a value, but only returns the
  475. * char code of the first character.
  476. *
  477. * @param {*} value Any character
  478. * @returns {int} The value's char code
  479. */
  480. function dec(value) {
  481. return value.toString().charCodeAt(0);
  482. }