123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867 |
- /**
- * The code box class
- */
- class CodeBox {
- constructor(codeBoxID, initialStackID, outputID) {
- /**
- * Possible vectors the pointer can move in
- * @type {Object}
- */
- this.directions = {
- NORTH: [ 0, -1],
- EAST: [ 1, 0],
- SOUTH: [ 0, 1],
- WEST: [-1, 0],
- };
- /**
- * The current vector of the pointer
- * @type {int[]}
- */
- this.curr_direction = this.directions.EAST;
- /**
- * The Set of instructions to execute
- *
- * Either a 1 or 2-dimensional array
- * @type {Array|Array[]}
- */
- this.box = [];
- /**
- * The farthest right the box goes
- * @type {int}
- */
- this.maxBoxWidth = 0;
- /**
- * The bottom of the box
- *
- * @type {int}
- */
- this.maxBoxHeight = 0;
- /**
- * The coordinates of the currently executing instruction inside the code box
- * @type {Object}
- */
- this.pointer = {
- X: 0,
- Y: 0,
- };
- /**
- * Was the instruction last moving in the left direction
- *
- * Used by the {@link Fisherman}
- * @type {boolean}
- */
- this.dirWasLeft = false;
- /**
- * Are we currently under the influence of the {@link Fisherman}
- * @type {boolean}
- */
- this.onTheHook = false;
- /**
- * Are we currently processing code box instructions as a string
- *
- * 0 when false, otherwise it holds the char code for string delimiter, either
- * 34 or 39
- *
- * @type {int}
- */
- this.stringMode = 0;
- /**
- * A list of stacks for the script to work with
- *
- * @type {Stack[]}
- */
- this.stacks = [new Stack()];
- /**
- * The index of the currently used stack
- *
- * @type {int}
- */
- this.curr_stack = 0;
- /**
- * The current date
- *
- * This value is updated every tick
- * @type {?Date}
- */
- this.datetime = null;
- this.codeBoxDOM = document.getElementById(codeBoxID);
- if(!this.codeBoxDOM) {
- throw new Error(`Failed to find textarea with ID: ${codeBoxID}`);
- }
- this.outputDOM = document.getElementById(outputID);
- if(!this.outputDOM) {
- throw new Error(`Failed to find textarea with ID: ${outputID}`);
- }
- this.initialStackDOM = document.getElementById(initialStackID);
- if(!this.initialStackDOM) {
- throw new Error(`Failed to find input with ID: ${initialStackID}`);
- }
- }
- /**
- * Parse the initial code box
- *
- * Transforms the textual code box into usable matrix
- */
- ParseCodeBox() {
- // Reset some fields for a clean run
- this.box = [];
- this.stacks = [new Stack()];
- this.curr_stack = 0;
- this.pointer = {X: 0, Y: 0};
- this.curr_direction = this.directions.EAST;
- this.outputDOM.value = "";
- const cbRaw = this.codeBoxDOM.value;
- const rows = cbRaw.split("\n");
- let maxRowLength = 0;
- for(const row of rows) {
- const rowSplit = row.split("");
- // Store this for later processing
- while(rowSplit.length > maxRowLength) {
- maxRowLength = rowSplit.length
- }
- this.box.push(rowSplit);
- }
- this.EqualizeBoxWidth(maxRowLength);
- this.maxBoxWidth = maxRowLength - 1;
- this.maxBoxHeight = this.box.length - 1;
- if (this.initialStackDOM.value != "") {
- this.ParseInitialStack();
- }
- this.Run();
- }
- /**
- * Parse the value provided for the stack at run time
- */
- ParseInitialStack() {
- const separator = /(["'].+?["']|\d+)/g;
- const stackValues = this.initialStackDOM.value.split(separator).filter((v) => v.trim().length);
- for (const val of stackValues) {
- const intVal = parseInt(val);
- if (!Number.isNaN(intVal)) {
- this.stacks[this.curr_stack].Push(intVal);
- }
- else {
- let chars = val.substr(1, val.length - 2).split('');
- chars = chars.map((c) => dec(c));
- this.stacks[this.curr_stack].Push(chars);
- }
- }
- }
- /**
- * Make all the rows in the code box the same length
- *
- * All rows not long enough will have NOPs added until they're uniform in size.
- *
- * @param {int} [rowLength] The longest row in the code box
- */
- EqualizeBoxWidth(rowLength = null) {
- if(!rowLength) {
- for(const row of this.box) {
- if(row.length > rowLength) {
- rowLength = row.length;
- }
- }
- }
- for(const row of this.box) {
- while(row.length < rowLength) {
- row.push(" ");
- }
- }
- }
- /**
- * Print the value to the display
- *
- * @TODO Set up an actual display
- * @param {*} value
- */
- Output(value) {
- this.outputDOM.value += value;
- }
- /**
- * The main loop for the engine
- */
- Run() {
- let fin = null;
- try {
- while(!fin) {
- fin = this.Swim();
- }
- }
- catch(e) {
- console.error(e);
- }
- }
- Execute(instruction) {
- let output = null;
- try{
- switch(instruction) {
- // NOP
- case " ":
- break;
- // Numbers
- case "1":
- case "2":
- case "3":
- case "4":
- case "5":
- case "6":
- case "7":
- case "8":
- case "9":
- case "0":
- case "a":
- case "b":
- case "c":
- case "d":
- case "e":
- case "f":
- this.stacks[this.curr_stack].Push(parseInt(instruction, 16));
- break;
- // Operators
- case "+": {
- const x = this.stacks[this.curr_stack].Pop();
- const y = this.stacks[this.curr_stack].Pop();
- this.stacks[this.curr_stack].Push(y + x);
- break;
- }
- case "-": {
- const x = this.stacks[this.curr_stack].Pop();
- const y = this.stacks[this.curr_stack].Pop();
- this.stacks[this.curr_stack].Push(y - x);
- break;
- }
- case "*": {
- const x = this.stacks[this.curr_stack].Pop();
- const y = this.stacks[this.curr_stack].Pop();
- this.stacks[this.curr_stack].Push(y * x);
- break;
- }
- case ",": {
- const x = this.stacks[this.curr_stack].Pop();
- const y = this.stacks[this.curr_stack].Pop();
- this.stacks[this.curr_stack].Push(y / x);
- break;
- }
- case "%": {
- const x = this.stacks[this.curr_stack].Pop();
- const y = this.stacks[this.curr_stack].Pop();
- this.stacks[this.curr_stack].Push(y % x);
- break;
- }
- case "(": {
- const x = this.stacks[this.curr_stack].Pop();
- const y = this.stacks[this.curr_stack].Pop();
- this.stacks[this.curr_stack].Push(y < x ? 1 : 0);
- break;
- }
- case ")": {
- const x = this.stacks[this.curr_stack].Pop();
- const y = this.stacks[this.curr_stack].Pop();
- this.stacks[this.curr_stack].Push(y > x ? 1 : 0);
- break;
- }
- case "=": {
- const x = this.stacks[this.curr_stack].Pop();
- const y = this.stacks[this.curr_stack].Pop();
- this.stacks[this.curr_stack].push(y == x ? 1 : 0);
- break;
- }
- //String mode
- case "\"":
- case "'":
- this.stringMode = !!this.stringMode ? 0 : dec(instruction);
- break;
- // Movement
- case "^":
- this.MoveUp();
- break;
- case ">":
- this.MoveRight();
- break;
- case "v":
- this.MoveDown();
- break;
- case "<":
- this.MoveLeft();
- break;
- // Mirrors
- case "/":
- this.ReflectForward();
- break;
- case "\\":
- this.ReflectBack();
- break;
- case "_":
- this.VerticalMirror();
- break;
- case "|":
- this.HorizontalMirror();
- break;
- case "#":
- this.OmniMirror();
- break;
- // Trampolines
- case "!":
- this.Move();
- break;
- case "?":
- if(this.stacks[this.curr_stack].Pop() === 0){ this.Move(); }
- break;
- // Stack manipulation
- case "&": {
- if (this.stacks[this.curr_stack].register == null) {
- this.stacks[this.curr_stack].register = this.stacks[this.curr_stack].Pop();
- }
- else {
- this.stacks[this.curr_stack].Push(this.stacks[this.curr_stack].register);
- this.stacks[this.curr_stack].register = null;
- }
- break;
- }
- case ":":
- this.stacks[this.curr_stack].Duplicate();
- break;
- case "~":
- this.stacks[this.curr_stack].Remove();
- break;
- case "$":
- this.stacks[this.curr_stack].SwapTwo();
- break;
- case "@":
- this.stacks[this.curr_stack].SwapThree();
- break;
- case "{":
- this.stacks[this.curr_stack].ShiftLeft();
- break;
- case "}":
- this.stacks[this.curr_stack].ShiftRight();
- break;
- case "r":
- this.stacks[this.curr_stack].Reverse();
- break;
- case "l":
- this.stacks[this.curr_stack].PushLength();
- break;
- case "[": {
- this.SpliceStack(this.stacks[this.curr_stack].Pop());
- break;
- }
- case "]":
- this.CollapseStack();
- break;
- case "I": {
- this.curr_stack++;
- if (this.curr_stack >= this.stacks.length) {
- throw new RangeError("curr_stack value out of bounds");
- }
- break;
- }
- case "D": {
- this.curr_stack--;
- if (this.curr_stack < 0) {
- throw new RangeError("curr_stack value out of bounds");
- }
- break;
- }
- // Output
- case "n":
- output = this.stacks[this.curr_stack].Pop();
- break;
- case "o":
- output = String.fromCharCode(this.stacks[this.curr_stack].Pop());
- break;
- // Time
- case "S":
- setTimeout(this.Run.bind(this), this.stacks[this.curr_stack].Pop() * 100);
- this.Move();
- output = true;
- break;
- case "h":
- this.stacks[this.curr_stack].Push(this.datetime.getUTCHours());
- break;
- case "m":
- this.stacks[this.curr_stack].Push(this.datetime.getUTCMinutes());
- break;
- case "s":
- this.stacks[this.curr_stack].Push(this.datetime.getUTCSeconds());
- break;
- // Code box manipulation
- case "g":
- this.PushFromCodeBox();
- break;
- case "p":
- this.PlaceIntoCodeBox();
- break;
- // End execution
- case ";":
- output = true;
- break;
- default:
- throw new Error(`Unknown instruction: ${instruction}`);
- }
- }
- catch(e) {
- console.error(`Something smells fishy!\n${e != "" ? `${e}\n` : ""}Instruction: ${instruction}\nStack: ${JSON.stringify(this.stacks[this.curr_stack].stack)}`);
- return true;
- }
- return output;
- }
- Swim() {
- const instruction = this.box[this.pointer.Y][this.pointer.X];
- this.datetime = new Date();
- if(this.stringMode != 0 && dec(instruction) != this.stringMode) {
- this.stacks[this.curr_stack].Push(dec(instruction));
- }
- else {
- const exeResult = this.Execute(instruction);
- if(exeResult === true) {
- return true;
- }
- else if(exeResult != null) {
- this.Output(exeResult);
- }
- }
- this.Move();
- }
- Move() {
- let newX = this.pointer.X + this.curr_direction[0];
- let newY = this.pointer.Y + this.curr_direction[1];
- // Keep the X coord in the boxes bounds
- if(newX < 0) {
- newX = this.maxBoxWidth;
- }
- else if(newX > this.maxBoxWidth) {
- newX = 0;
- }
- // Keep the Y coord in the boxes bounds
- if(newY < 0) {
- newY = this.maxBoxHeight;
- }
- else if(newY > this.maxBoxHeight) {
- newY = 0;
- }
- this.SetPointer(newX, newY);
- }
- /**
- * Implement C and .
- */
- SetPointer(x, y) {
- this.pointer = {X: x, Y: y};
- }
- /**
- * Implement ^
- *
- * Changes the swim direction upward
- */
- MoveUp() {
- this.curr_direction = this.directions.NORTH;
- }
- /**
- * Implement >
- *
- * Changes the swim direction rightward
- */
- MoveRight() {
- this.curr_direction = this.directions.EAST;
- this.dirWasLeft = false;
- }
- /**
- * Implement v
- *
- * Changes the swim direction downward
- */
- MoveDown() {
- this.curr_direction = this.directions.SOUTH;
- }
- /**
- * Implement <
- *
- * Changes the swim direction leftward
- */
- MoveLeft() {
- this.curr_direction = this.directions.WEST;
- this.dirWasLeft = true;
- }
- /**
- * Implement /
- *
- * Reflects the swim direction depending on its starting value
- */
- ReflectForward() {
- if (this.curr_direction == this.directions.NORTH) {
- this.MoveRight();
- }
- else if (this.curr_direction == this.directions.EAST) {
- this.MoveUp();
- }
- else if (this.curr_direction == this.directions.SOUTH) {
- this.MoveLeft();
- }
- else {
- this.MoveDown();
- }
- }
- /**
- * Implement \
- *
- * Reflects the swim direction depending on its starting value
- */
- ReflectBack() {
- if (this.curr_direction == this.directions.NORTH) {
- this.MoveLeft();
- }
- else if (this.curr_direction == this.directions.EAST) {
- this.MoveDown();
- }
- else if (this.curr_direction == this.directions.SOUTH) {
- this.MoveRight();
- }
- else {
- this.MoveUp();
- }
- }
- /**
- * Implement |
- *
- * Swaps the horizontal swim direction to its opposite
- */
- HorizontalMirror() {
- if (this.curr_direction == this.directions.EAST) {
- this.MoveLeft();
- }
- else {
- this.MoveRight();
- }
- }
- /**
- * Implement _
- *
- * Swaps the horizontal swim direction to its opposite
- */
- VerticalMirror() {
- if (this.curr_direction == this.directions.NORTH) {
- this.MoveDown();
- }
- else {
- this.MoveUp();
- }
- }
- /**
- * Implement #
- *
- * A combination of the vertical and the horizontal mirror
- */
- OmniMirror() {
- if (this.curr_direction[0]) {
- this.VerticalMirror();
- }
- else {
- this.HorizontalMirror();
- }
- }
- /**
- * Implement x
- *
- * Pseudo-randomly switches the swim direction
- */
- ShuffleDirection() {
- this.curr_direction = Object.values(this.directions)[Math.floor(Math.random() * 4)];
- }
- /**
- * Implement [
- *
- * Takes X number of elements out of a stack and into a new stack
- *
- * This action creates a new stack, and places it on top of the one it was created from.
- * So, if you have three stacks, A, B, and C, and you splice a stack off of stack B,
- * the new order will be: A, B, D, and C.
- *
- * @see {@link https://esolangs.org/wiki/Fish#Stacks ><> Documentation}
- *
- * @param {int} spliceCount The number of elements to pop into a new stack
- */
- SpliceStack(spliceCount) {
- const stackCount = this.stacks[this.curr_stack].stack.length;
- if (spliceCount > stackCount) {
- throw new RangeError(`Cannot remove ${spliceCount} elements from a stack of only ${stackCount} elements`);
- }
- const newStack = new Stack(this.stacks[this.curr_stack].stack.splice(stackCount - spliceCount, spliceCount));
- // We're at the top of the stacks stack, so we can use .push
- if (this.curr_stack == this.stacks.length - 1) {
- this.stacks.push(newStack);
- }
- else {
- this.stacks.splice(this.curr_stack + 1, 0, newStack);
- }
- this.curr_stack++;
- }
- /**
- * Implement ]
- *
- * Collapses the current stack onto the one below it
- * If the current stack is the only one, it is replaced with a blank stack
- */
- CollapseStack() {
- // Undefined behavior collapsing the first stack down when there are other stacks available
- if (this.curr_stack == 0 && this.stacks.length != 1) {
- throw new Error();
- }
- if (this.curr_stack == 0) {
- this.stacks = [new Stack()];
- }
- else {
- const collapsed = this.stacks.splice(this.curr_stack, 1).pop();
- this.curr_stack--;
- const currStackCount = this.stacks[this.curr_stack].stack.length;
- this.stacks[this.curr_stack].stack.splice(currStackCount, 0, ...collapsed.stack);
- }
- }
- /**
- * Implement g
- *
- * Pops `y` and `x` from the stack, and then pushes the value of the character
- * at `[x, y]` in the code box.
- *
- * NOP's and coords that are out of bounds are converted to 0.
- *
- * 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}
- */
- PushFromCodeBox() {
- const y = this.stacks[this.curr_stack].Pop();
- const x = this.stacks[this.curr_stack].Pop();
- let val = undefined;
- try {
- val = this.box[y][x] || " ";
- }
- catch (e) {
- val = " ";
- }
- const valParsed = val == " " ? 0 : dec(val);
- this.stacks[this.curr_stack].Push(valParsed);
- }
- /**
- * Implement p
- *
- * Pops `y`, `x`, and `v` off of the stack, and then places the string
- * representation of that value at `[x, y]` in the code box.
- */
- PlaceIntoCodeBox() {
- const y = this.stacks[this.curr_stack].Pop();
- const x = this.stacks[this.curr_stack].Pop();
- const v = this.stacks[this.curr_stack].Pop();
- while(y >= this.box.length) {
- this.box.push([]);
- }
- while(x >= this.box[y].length) {
- this.box[y].push(" ");
- }
- this.EqualizeBoxWidth();
- this.box[y][x] = String.fromCharCode(v);
- }
- /**
- * Implement `
- *
- * Changes the swim direction based on the previous direction
- * @see https://esolangs.org/wiki/Starfish#Fisherman
- */
- Fisherman() {
- if (this.curr_direction[0]) {
- if (this.dirWasLeft) {
- this.MoveLeft();
- }
- else {
- this.MoveRight();
- }
- }
- else {
- if (this.onTheHook) {
- this.onTheHook = false;
- this.MoveUp();
- }
- else {
- this.onTheHook = true;
- this.MoveDown();
- }
- }
- }
- }
- /**
- * The stack class
- */
- class Stack {
- /**
- * @param {int[]} stackValues An array of values to initialize the stack with
- */
- constructor(stackValues = []) {
- /**
- * The stack
- * @type {int[]}
- */
- this.stack = stackValues;
- /**
- * A single value saved off the stack
- * @type {int}
- */
- this.register = null;
- }
- /**
- * Wrapper function for Array.prototype.push
- * @param {*} newValue
- */
- Push(newValue) {
- if(Array.isArray(newValue)) {
- this.stack.push(...newValue);
- }
- else {
- this.stack.push(newValue);
- }
- }
- /**
- * Wrapper function for Array.prototype.pop
- * @returns {*}
- */
- Pop() {
- const value = this.stack.pop();
- if(value == undefined){ throw new Error(); }
- return value;
- }
- /**
- * Implement }
- *
- * Shifts the entire stack leftward by one value
- */
- ShiftLeft() {
- const temp = this.stack.shift();
- this.stack.push(temp);
- }
- /**
- * Implement {
- *
- * Shifts the entire stack rightward by one value
- */
- ShiftRight() {
- const temp = this.stack.pop();
- this.stack.unshift(temp);
- }
- /**
- * Implement $
- *
- * Swaps the top two values of the stack
- */
- SwapTwo() {
- if(this.stack.length < 2) { throw new Error(); }
- const popped = this.stack.splice(this.stack.length - 2, 2);
- this.stack.push(...popped.reverse());
- }
- /**
- * Implement @
- *
- * Swaps the top three values of the stack
- */
- SwapThree() {
- if(this.stack.length < 3) { throw new Error(); }
- // Get the top three values
- const popped = this.stack.splice(this.stack.length - 3, 3);
- // Shift the elements to the right
- popped.unshift(popped.pop());
- this.stack.push(...popped);
- }
- /**
- * Implement :
- *
- * Duplicates the element on the top of the stack
- */
- Duplicate() {
- this.stack.push(this.stack[this.stack.length-1]);
- }
- /**
- * Implements ~
- *
- * Removes the element on the top of the stack
- */
- Remove() {
- this.stack.pop();
- }
- /**
- * Implement r
- *
- * Reverses the entire stack
- */
- Reverse() {
- this.stack.reverse();
- }
- /**
- * Implement l
- *
- * Pushes the length of the stack onto the top of the stack
- */
- PushLength() {
- this.stack.push(this.stack.length);
- }
- }
- /**
- * Get the char code of any character
- *
- * Can actually take any length of a value, but only returns the
- * char code of the first character.
- *
- * @param {*} value Any character
- * @returns {int} The value's char code
- */
- function dec(value) {
- return value.toString().charCodeAt(0);
- }
|