starfish.js 28 KB

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