starfish.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973
  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 farthest right the box goes
  30. * @type {int}
  31. */
  32. this.maxBoxWidth = 0;
  33. /**
  34. * The bottom of the box
  35. *
  36. * @type {int}
  37. */
  38. this.maxBoxHeight = 0;
  39. /**
  40. * The coordinates of the currently executing instruction inside the code box
  41. * @type {Object}
  42. */
  43. this.pointer = {
  44. X: 0,
  45. Y: 0,
  46. };
  47. /**
  48. * Was the instruction last moving in the left direction
  49. *
  50. * Used by the {@link Fisherman}
  51. * @type {boolean}
  52. */
  53. this.dirWasLeft = false;
  54. /**
  55. * Are we currently under the influence of the {@link Fisherman}
  56. * @type {boolean}
  57. */
  58. this.onTheHook = false;
  59. /**
  60. * Have we dived using `u`
  61. *
  62. * Until rising with `O`, all instructions except movement and mirror instructions are ignored.
  63. * @type {boolean}
  64. */
  65. this.hasDove = false;
  66. /**
  67. * Constant of values to check when `this.hasDove == true`
  68. *
  69. * Includes `O` so that we can rise.
  70. *
  71. * @type {string[]}
  72. */
  73. this.MOVEMENT_AND_MIRRORS = [">", "<", "^", "v", "/", "\\", "|", "_", "#", "x", "`", "O"];
  74. /**
  75. * Are we currently processing code box instructions as a string
  76. *
  77. * 0 when false, otherwise it holds the char code for string delimiter, either
  78. * 34 or 39
  79. *
  80. * @type {int}
  81. */
  82. this.stringMode = 0;
  83. /**
  84. * A list of stacks for the script to work with
  85. *
  86. * @type {Stack[]}
  87. */
  88. this.stacks = [new Stack()];
  89. /**
  90. * The index of the currently used stack
  91. *
  92. * @type {int}
  93. */
  94. this.curr_stack = 0;
  95. /**
  96. * The current date
  97. *
  98. * This value is updated every tick
  99. * @type {?Date}
  100. */
  101. this.datetime = null;
  102. /**
  103. * Assorted debug options
  104. *
  105. * @type {object}
  106. */
  107. this.debug = {
  108. print: {
  109. codeBox: false,
  110. stacks: false,
  111. }
  112. };
  113. this.codeBoxDOM = document.getElementById(codeBoxID);
  114. if(!this.codeBoxDOM) {
  115. throw new Error(`Failed to find textarea with ID: ${codeBoxID}`);
  116. }
  117. this.outputDOM = document.getElementById(outputID);
  118. if(!this.outputDOM) {
  119. throw new Error(`Failed to find textarea with ID: ${outputID}`);
  120. }
  121. this.initialStackDOM = document.getElementById(initialStackID);
  122. if(!this.initialStackDOM) {
  123. throw new Error(`Failed to find input with ID: ${initialStackID}`);
  124. }
  125. }
  126. /**
  127. * Parse the initial code box
  128. *
  129. * Transforms the textual code box into usable matrix
  130. */
  131. ParseCodeBox() {
  132. // Reset some fields for a clean run
  133. this.box = [];
  134. this.stacks = [new Stack()];
  135. this.curr_stack = 0;
  136. this.pointer = {X: 0, Y: 0};
  137. this.curr_direction = this.directions.EAST;
  138. this.outputDOM.value = "";
  139. console.clear();
  140. // Set the debug values
  141. this.debug.print.codeBox = document.getElementById("debug-code-box")?.checked;
  142. this.debug.print.stacks = document.getElementById("debug-stacks")?.checked;
  143. const cbRaw = this.codeBoxDOM.value;
  144. const rows = cbRaw.split("\n");
  145. let maxRowLength = 0;
  146. for(const row of rows) {
  147. const rowSplit = row.split("");
  148. // Store this for later processing
  149. while(rowSplit.length > maxRowLength) {
  150. maxRowLength = rowSplit.length
  151. }
  152. this.box.push(rowSplit);
  153. }
  154. this.EqualizeBoxWidth(maxRowLength);
  155. this.maxBoxWidth = maxRowLength - 1;
  156. this.maxBoxHeight = this.box.length - 1;
  157. if (this.initialStackDOM.value != "") {
  158. this.ParseInitialStack();
  159. }
  160. this.Run();
  161. }
  162. /**
  163. * Parse the value provided for the stack at run time
  164. */
  165. ParseInitialStack() {
  166. const separator = /(["'].+?["']|\d+)/g;
  167. const stackValues = this.initialStackDOM.value.split(separator).filter((v) => v.trim().length);
  168. for (const val of stackValues) {
  169. const intVal = parseInt(val);
  170. if (!Number.isNaN(intVal)) {
  171. this.stacks[this.curr_stack].Push(intVal);
  172. }
  173. else {
  174. let chars = val.substr(1, val.length - 2).split('');
  175. chars = chars.map((c) => dec(c));
  176. this.stacks[this.curr_stack].Push(chars);
  177. }
  178. }
  179. }
  180. /**
  181. * Prints the code box to the console
  182. */
  183. PrintCodeBox() {
  184. let output = "";
  185. for (let y = 0; y < this.box.length; y++) {
  186. for (let x = 0; x < this.box[y].length; x++) {
  187. let instruction = this.box[y][x];
  188. if (x == this.pointer.X && y == this.pointer.Y) {
  189. instruction = `*${instruction}*`;
  190. }
  191. output += `${instruction} `;
  192. }
  193. output += "\n";
  194. }
  195. console.log(output);
  196. }
  197. /**
  198. * Prints all stacks to the console
  199. */
  200. PrintStacks() {
  201. let output = "{\n";
  202. for (let i = 0; i < this.stacks.length; i++) {
  203. output += `\t${i}: ${JSON.stringify(this.stacks[i].stack)},\n`
  204. }
  205. output += "}";
  206. console.log(output);
  207. }
  208. /**
  209. * Make all the rows in the code box the same length
  210. *
  211. * All rows not long enough will have NOPs added until they're uniform in size.
  212. *
  213. * @param {int} [rowLength] The longest row in the code box
  214. */
  215. EqualizeBoxWidth(rowLength = null) {
  216. if(!rowLength) {
  217. for(const row of this.box) {
  218. if(row.length > rowLength) {
  219. rowLength = row.length;
  220. }
  221. }
  222. }
  223. for(const row of this.box) {
  224. while(row.length < rowLength) {
  225. row.push(" ");
  226. }
  227. }
  228. }
  229. /**
  230. * Print the value to the display
  231. *
  232. * @TODO Set up an actual display
  233. * @param {*} value
  234. */
  235. Output(value) {
  236. this.outputDOM.value += value;
  237. }
  238. /**
  239. * The main loop for the engine
  240. */
  241. Run() {
  242. let fin = null;
  243. try {
  244. while(!fin) {
  245. fin = this.Swim();
  246. }
  247. }
  248. catch(e) {
  249. console.error(e);
  250. }
  251. }
  252. Execute(instruction) {
  253. let output = null;
  254. // Ignore non-movement and non-mirror instructions
  255. if (this.hasDove) {
  256. if (this.MOVEMENT_AND_MIRRORS.indexOf(instruction) < 0) {
  257. return output;
  258. }
  259. }
  260. try{
  261. switch(instruction) {
  262. // NOP
  263. case " ":
  264. break;
  265. // Numbers
  266. case "1":
  267. case "2":
  268. case "3":
  269. case "4":
  270. case "5":
  271. case "6":
  272. case "7":
  273. case "8":
  274. case "9":
  275. case "0":
  276. case "a":
  277. case "b":
  278. case "c":
  279. case "d":
  280. case "e":
  281. case "f":
  282. this.stacks[this.curr_stack].Push(parseInt(instruction, 16));
  283. break;
  284. // Operators
  285. case "+": {
  286. const x = this.stacks[this.curr_stack].Pop();
  287. const y = this.stacks[this.curr_stack].Pop();
  288. this.stacks[this.curr_stack].Push(y + x);
  289. break;
  290. }
  291. case "-": {
  292. const x = this.stacks[this.curr_stack].Pop();
  293. const y = this.stacks[this.curr_stack].Pop();
  294. this.stacks[this.curr_stack].Push(y - x);
  295. break;
  296. }
  297. case "*": {
  298. const x = this.stacks[this.curr_stack].Pop();
  299. const y = this.stacks[this.curr_stack].Pop();
  300. this.stacks[this.curr_stack].Push(y * x);
  301. break;
  302. }
  303. case ",": {
  304. const x = this.stacks[this.curr_stack].Pop();
  305. const y = this.stacks[this.curr_stack].Pop();
  306. this.stacks[this.curr_stack].Push(y / x);
  307. break;
  308. }
  309. case "%": {
  310. const x = this.stacks[this.curr_stack].Pop();
  311. const y = this.stacks[this.curr_stack].Pop();
  312. this.stacks[this.curr_stack].Push(y % x);
  313. break;
  314. }
  315. case "(": {
  316. const x = this.stacks[this.curr_stack].Pop();
  317. const y = this.stacks[this.curr_stack].Pop();
  318. this.stacks[this.curr_stack].Push(y < x ? 1 : 0);
  319. break;
  320. }
  321. case ")": {
  322. const x = this.stacks[this.curr_stack].Pop();
  323. const y = this.stacks[this.curr_stack].Pop();
  324. this.stacks[this.curr_stack].Push(y > x ? 1 : 0);
  325. break;
  326. }
  327. case "=": {
  328. const x = this.stacks[this.curr_stack].Pop();
  329. const y = this.stacks[this.curr_stack].Pop();
  330. this.stacks[this.curr_stack].push(y == x ? 1 : 0);
  331. break;
  332. }
  333. //String mode
  334. case "\"":
  335. case "'":
  336. this.stringMode = !!this.stringMode ? 0 : dec(instruction);
  337. break;
  338. // Dive, Rise and Fisherman
  339. case "u":
  340. this.hasDove = true;
  341. break;
  342. case "O":
  343. this.hasDove = false;
  344. break;
  345. case "`":
  346. // TODO
  347. break;
  348. // Movement
  349. case "^":
  350. this.MoveUp();
  351. break;
  352. case ">":
  353. this.MoveRight();
  354. break;
  355. case "v":
  356. this.MoveDown();
  357. break;
  358. case "<":
  359. this.MoveLeft();
  360. break;
  361. // Mirrors
  362. case "/":
  363. this.ReflectForward();
  364. break;
  365. case "\\":
  366. this.ReflectBack();
  367. break;
  368. case "_":
  369. this.VerticalMirror();
  370. break;
  371. case "|":
  372. this.HorizontalMirror();
  373. break;
  374. case "#":
  375. this.OmniMirror();
  376. break;
  377. // Trampolines
  378. case "!":
  379. this.Move();
  380. break;
  381. case "?":
  382. if(this.stacks[this.curr_stack].Pop() === 0){ this.Move(); }
  383. break;
  384. // Stack manipulation
  385. case "&": {
  386. if (this.stacks[this.curr_stack].register == null) {
  387. this.stacks[this.curr_stack].register = this.stacks[this.curr_stack].Pop();
  388. }
  389. else {
  390. this.stacks[this.curr_stack].Push(this.stacks[this.curr_stack].register);
  391. this.stacks[this.curr_stack].register = null;
  392. }
  393. break;
  394. }
  395. case ":":
  396. this.stacks[this.curr_stack].Duplicate();
  397. break;
  398. case "~":
  399. this.stacks[this.curr_stack].Remove();
  400. break;
  401. case "$":
  402. this.stacks[this.curr_stack].SwapTwo();
  403. break;
  404. case "@":
  405. this.stacks[this.curr_stack].SwapThree();
  406. break;
  407. case "{":
  408. this.stacks[this.curr_stack].ShiftLeft();
  409. break;
  410. case "}":
  411. this.stacks[this.curr_stack].ShiftRight();
  412. break;
  413. case "r":
  414. this.stacks[this.curr_stack].Reverse();
  415. break;
  416. case "l":
  417. this.stacks[this.curr_stack].PushLength();
  418. break;
  419. case "[": {
  420. this.SpliceStack(this.stacks[this.curr_stack].Pop());
  421. break;
  422. }
  423. case "]":
  424. this.CollapseStack();
  425. break;
  426. case "I": {
  427. this.curr_stack++;
  428. if (this.curr_stack >= this.stacks.length) {
  429. throw new RangeError("curr_stack value out of bounds");
  430. }
  431. break;
  432. }
  433. case "D": {
  434. this.curr_stack--;
  435. if (this.curr_stack < 0) {
  436. throw new RangeError("curr_stack value out of bounds");
  437. }
  438. break;
  439. }
  440. // Output
  441. case "n":
  442. output = this.stacks[this.curr_stack].Pop();
  443. break;
  444. case "o":
  445. output = String.fromCharCode(this.stacks[this.curr_stack].Pop());
  446. break;
  447. // Time
  448. case "S":
  449. setTimeout(this.Run.bind(this), this.stacks[this.curr_stack].Pop() * 100);
  450. this.Move();
  451. output = true;
  452. break;
  453. case "h":
  454. this.stacks[this.curr_stack].Push(this.datetime.getUTCHours());
  455. break;
  456. case "m":
  457. this.stacks[this.curr_stack].Push(this.datetime.getUTCMinutes());
  458. break;
  459. case "s":
  460. this.stacks[this.curr_stack].Push(this.datetime.getUTCSeconds());
  461. break;
  462. // Code box manipulation
  463. case "g":
  464. this.PushFromCodeBox();
  465. break;
  466. case "p":
  467. this.PlaceIntoCodeBox();
  468. break;
  469. // Functions
  470. case ".": {
  471. this.pointer.Y = this.stacks[this.curr_stack].Pop();
  472. this.pointer.X = this.stacks[this.curr_stack].Pop();
  473. break;
  474. }
  475. case "C": {
  476. const currCoords = new Stack([this.pointer.X, this.pointer.Y]);
  477. this.stacks.splice(this.curr_stack, 0, currCoords);
  478. this.curr_stack++;
  479. this.pointer.Y = this.stacks[this.curr_stack].Pop();
  480. this.pointer.X = this.stacks[this.curr_stack].Pop();
  481. break;
  482. }
  483. case "R": {
  484. const newCoords = this.stacks.splice(this.curr_stack - 1, 1).pop();
  485. this.curr_stack--;
  486. this.pointer.Y = newCoords.Pop()
  487. this.pointer.X = newCoords.Pop();
  488. break;
  489. }
  490. // End execution
  491. case ";":
  492. output = true;
  493. break;
  494. default:
  495. throw new Error(`Unknown instruction: ${instruction}`);
  496. }
  497. }
  498. catch(e) {
  499. console.error(`Something smells fishy!\n${e != "" ? `${e}\n` : ""}Instruction: ${instruction}\nStack: ${JSON.stringify(this.stacks[this.curr_stack].stack)}`);
  500. return true;
  501. }
  502. return output;
  503. }
  504. Swim() {
  505. if(this.debug.print.codeBox) { this.PrintCodeBox(); }
  506. if(this.debug.print.stacks) { this.PrintStacks(); }
  507. const instruction = this.box[this.pointer.Y][this.pointer.X];
  508. this.datetime = new Date();
  509. if(this.stringMode != 0 && dec(instruction) != this.stringMode) {
  510. this.stacks[this.curr_stack].Push(dec(instruction));
  511. }
  512. else {
  513. const exeResult = this.Execute(instruction);
  514. if(exeResult === true) {
  515. return true;
  516. }
  517. else if(exeResult != null) {
  518. this.Output(exeResult);
  519. }
  520. }
  521. this.Move();
  522. }
  523. Move() {
  524. let newX = this.pointer.X + this.curr_direction[0];
  525. let newY = this.pointer.Y + this.curr_direction[1];
  526. // Keep the X coord in the boxes bounds
  527. if(newX < 0) {
  528. newX = this.maxBoxWidth;
  529. }
  530. else if(newX > this.maxBoxWidth) {
  531. newX = 0;
  532. }
  533. // Keep the Y coord in the boxes bounds
  534. if(newY < 0) {
  535. newY = this.maxBoxHeight;
  536. }
  537. else if(newY > this.maxBoxHeight) {
  538. newY = 0;
  539. }
  540. this.SetPointer(newX, newY);
  541. }
  542. /**
  543. * Implement C and .
  544. */
  545. SetPointer(x, y) {
  546. this.pointer = {X: x, Y: y};
  547. }
  548. /**
  549. * Implement ^
  550. *
  551. * Changes the swim direction upward
  552. */
  553. MoveUp() {
  554. this.curr_direction = this.directions.NORTH;
  555. }
  556. /**
  557. * Implement >
  558. *
  559. * Changes the swim direction rightward
  560. */
  561. MoveRight() {
  562. this.curr_direction = this.directions.EAST;
  563. this.dirWasLeft = false;
  564. }
  565. /**
  566. * Implement v
  567. *
  568. * Changes the swim direction downward
  569. */
  570. MoveDown() {
  571. this.curr_direction = this.directions.SOUTH;
  572. }
  573. /**
  574. * Implement <
  575. *
  576. * Changes the swim direction leftward
  577. */
  578. MoveLeft() {
  579. this.curr_direction = this.directions.WEST;
  580. this.dirWasLeft = true;
  581. }
  582. /**
  583. * Implement /
  584. *
  585. * Reflects the swim direction depending on its starting value
  586. */
  587. ReflectForward() {
  588. if (this.curr_direction == this.directions.NORTH) {
  589. this.MoveRight();
  590. }
  591. else if (this.curr_direction == this.directions.EAST) {
  592. this.MoveUp();
  593. }
  594. else if (this.curr_direction == this.directions.SOUTH) {
  595. this.MoveLeft();
  596. }
  597. else {
  598. this.MoveDown();
  599. }
  600. }
  601. /**
  602. * Implement \
  603. *
  604. * Reflects the swim direction depending on its starting value
  605. */
  606. ReflectBack() {
  607. if (this.curr_direction == this.directions.NORTH) {
  608. this.MoveLeft();
  609. }
  610. else if (this.curr_direction == this.directions.EAST) {
  611. this.MoveDown();
  612. }
  613. else if (this.curr_direction == this.directions.SOUTH) {
  614. this.MoveRight();
  615. }
  616. else {
  617. this.MoveUp();
  618. }
  619. }
  620. /**
  621. * Implement |
  622. *
  623. * Swaps the horizontal swim direction to its opposite
  624. */
  625. HorizontalMirror() {
  626. if (this.curr_direction == this.directions.EAST) {
  627. this.MoveLeft();
  628. }
  629. else {
  630. this.MoveRight();
  631. }
  632. }
  633. /**
  634. * Implement _
  635. *
  636. * Swaps the horizontal swim direction to its opposite
  637. */
  638. VerticalMirror() {
  639. if (this.curr_direction == this.directions.NORTH) {
  640. this.MoveDown();
  641. }
  642. else {
  643. this.MoveUp();
  644. }
  645. }
  646. /**
  647. * Implement #
  648. *
  649. * A combination of the vertical and the horizontal mirror
  650. */
  651. OmniMirror() {
  652. if (this.curr_direction[0]) {
  653. this.VerticalMirror();
  654. }
  655. else {
  656. this.HorizontalMirror();
  657. }
  658. }
  659. /**
  660. * Implement x
  661. *
  662. * Pseudo-randomly switches the swim direction
  663. */
  664. ShuffleDirection() {
  665. this.curr_direction = Object.values(this.directions)[Math.floor(Math.random() * 4)];
  666. }
  667. /**
  668. * Implement [
  669. *
  670. * Takes X number of elements out of a stack and into a new stack
  671. *
  672. * This action creates a new stack, and places it on top of the one it was created from.
  673. * So, if you have three stacks, A, B, and C, and you splice a stack off of stack B,
  674. * the new order will be: A, B, D, and C.
  675. *
  676. * @see {@link https://esolangs.org/wiki/Fish#Stacks ><> Documentation}
  677. *
  678. * @param {int} spliceCount The number of elements to pop into a new stack
  679. */
  680. SpliceStack(spliceCount) {
  681. const stackCount = this.stacks[this.curr_stack].stack.length;
  682. if (spliceCount > stackCount) {
  683. throw new RangeError(`Cannot remove ${spliceCount} elements from a stack of only ${stackCount} elements`);
  684. }
  685. const newStack = new Stack(this.stacks[this.curr_stack].stack.splice(stackCount - spliceCount, spliceCount));
  686. // We're at the top of the stacks stack, so we can use .push
  687. if (this.curr_stack == this.stacks.length - 1) {
  688. this.stacks.push(newStack);
  689. }
  690. else {
  691. this.stacks.splice(this.curr_stack + 1, 0, newStack);
  692. }
  693. this.curr_stack++;
  694. }
  695. /**
  696. * Implement ]
  697. *
  698. * Collapses the current stack onto the one below it
  699. * If the current stack is the only one, it is replaced with a blank stack
  700. */
  701. CollapseStack() {
  702. // Undefined behavior collapsing the first stack down when there are other stacks available
  703. if (this.curr_stack == 0 && this.stacks.length != 1) {
  704. throw new Error();
  705. }
  706. if (this.curr_stack == 0) {
  707. this.stacks = [new Stack()];
  708. }
  709. else {
  710. const collapsed = this.stacks.splice(this.curr_stack, 1).pop();
  711. this.curr_stack--;
  712. const currStackCount = this.stacks[this.curr_stack].stack.length;
  713. this.stacks[this.curr_stack].stack.splice(currStackCount, 0, ...collapsed.stack);
  714. }
  715. }
  716. /**
  717. * Implement g
  718. *
  719. * Pops `y` and `x` from the stack, and then pushes the value of the character
  720. * at `[x, y]` in the code box.
  721. *
  722. * NOP's and coords that are out of bounds are converted to 0.
  723. *
  724. * Implements the behavior as defined by the original {@link https://gist.github.com/anonymous/6392418#file-fish-py-L306 ><>}, and not {@link https://github.com/redstarcoder/go-starfish/blob/master/starfish/starfish.go#L378 go-starfish}
  725. */
  726. PushFromCodeBox() {
  727. const y = this.stacks[this.curr_stack].Pop();
  728. const x = this.stacks[this.curr_stack].Pop();
  729. let val = undefined;
  730. try {
  731. val = this.box[y][x] || " ";
  732. }
  733. catch (e) {
  734. val = " ";
  735. }
  736. const valParsed = val == " " ? 0 : dec(val);
  737. this.stacks[this.curr_stack].Push(valParsed);
  738. }
  739. /**
  740. * Implement p
  741. *
  742. * Pops `y`, `x`, and `v` off of the stack, and then places the string
  743. * representation of that value at `[x, y]` in the code box.
  744. */
  745. PlaceIntoCodeBox() {
  746. const y = this.stacks[this.curr_stack].Pop();
  747. const x = this.stacks[this.curr_stack].Pop();
  748. const v = this.stacks[this.curr_stack].Pop();
  749. while(y >= this.box.length) {
  750. this.box.push([]);
  751. }
  752. while(x >= this.box[y].length) {
  753. this.box[y].push(" ");
  754. }
  755. this.EqualizeBoxWidth();
  756. this.box[y][x] = String.fromCharCode(v);
  757. }
  758. /**
  759. * Implement `
  760. *
  761. * Changes the swim direction based on the previous direction
  762. * @see https://esolangs.org/wiki/Starfish#Fisherman
  763. */
  764. Fisherman() {
  765. if (this.curr_direction[0]) {
  766. if (this.dirWasLeft) {
  767. this.MoveLeft();
  768. }
  769. else {
  770. this.MoveRight();
  771. }
  772. }
  773. else {
  774. if (this.onTheHook) {
  775. this.onTheHook = false;
  776. this.MoveUp();
  777. }
  778. else {
  779. this.onTheHook = true;
  780. this.MoveDown();
  781. }
  782. }
  783. }
  784. }
  785. /**
  786. * The stack class
  787. */
  788. class Stack {
  789. /**
  790. * @param {int[]} stackValues An array of values to initialize the stack with
  791. */
  792. constructor(stackValues = []) {
  793. /**
  794. * The stack
  795. * @type {int[]}
  796. */
  797. this.stack = stackValues;
  798. /**
  799. * A single value saved off the stack
  800. * @type {int}
  801. */
  802. this.register = null;
  803. }
  804. /**
  805. * Wrapper function for Array.prototype.push
  806. * @param {*} newValue
  807. */
  808. Push(newValue) {
  809. if(Array.isArray(newValue)) {
  810. this.stack.push(...newValue);
  811. }
  812. else {
  813. this.stack.push(newValue);
  814. }
  815. }
  816. /**
  817. * Wrapper function for Array.prototype.pop
  818. * @returns {*}
  819. */
  820. Pop() {
  821. const value = this.stack.pop();
  822. if(value == undefined){ throw new Error(); }
  823. return value;
  824. }
  825. /**
  826. * Implement }
  827. *
  828. * Shifts the entire stack leftward by one value
  829. */
  830. ShiftLeft() {
  831. const temp = this.stack.shift();
  832. this.stack.push(temp);
  833. }
  834. /**
  835. * Implement {
  836. *
  837. * Shifts the entire stack rightward by one value
  838. */
  839. ShiftRight() {
  840. const temp = this.stack.pop();
  841. this.stack.unshift(temp);
  842. }
  843. /**
  844. * Implement $
  845. *
  846. * Swaps the top two values of the stack
  847. */
  848. SwapTwo() {
  849. if(this.stack.length < 2) { throw new Error(); }
  850. const popped = this.stack.splice(this.stack.length - 2, 2);
  851. this.stack.push(...popped.reverse());
  852. }
  853. /**
  854. * Implement @
  855. *
  856. * Swaps the top three values of the stack
  857. */
  858. SwapThree() {
  859. if(this.stack.length < 3) { throw new Error(); }
  860. // Get the top three values
  861. const popped = this.stack.splice(this.stack.length - 3, 3);
  862. // Shift the elements to the right
  863. popped.unshift(popped.pop());
  864. this.stack.push(...popped);
  865. }
  866. /**
  867. * Implement :
  868. *
  869. * Duplicates the element on the top of the stack
  870. */
  871. Duplicate() {
  872. this.stack.push(this.stack[this.stack.length-1]);
  873. }
  874. /**
  875. * Implements ~
  876. *
  877. * Removes the element on the top of the stack
  878. */
  879. Remove() {
  880. this.stack.pop();
  881. }
  882. /**
  883. * Implement r
  884. *
  885. * Reverses the entire stack
  886. */
  887. Reverse() {
  888. this.stack.reverse();
  889. }
  890. /**
  891. * Implement l
  892. *
  893. * Pushes the length of the stack onto the top of the stack
  894. */
  895. PushLength() {
  896. this.stack.push(this.stack.length);
  897. }
  898. }
  899. /**
  900. * Get the char code of any character
  901. *
  902. * Can actually take any length of a value, but only returns the
  903. * char code of the first character.
  904. *
  905. * @param {*} value Any character
  906. * @returns {int} The value's char code
  907. */
  908. function dec(value) {
  909. return value.toString().charCodeAt(0);
  910. }