/* Flunger v0.6 Befunge93 interpreter in flash by Jim Susinno 2005 Some crunchiness here when trying to scroll stdout while program is running - drag may not be stopped properly TODO: 6/8/2005 - verbosity switch? - instructions/second speed counter dev notes 7/6/05: added strict state toggle button and flag mc - is there a way to fold this into just one movie clip? scott would know... also added stderr output box - maybe theres a better place to put it. maybe not. ___Function list:___ function loadFungeFromNet() function initFunge() function initTrails(x:Number) function pcBoundaryConstrain() function fungePush(byte) function fungePop():Number function assembleStackString() function updateStdout() function updateStdoutScroll() function updateStdoutLineCount() function print_stderr(String) function getIntFromStdin() function getCharFromStdin() function getInstruction(x:Number, y:Number) function putInstruction(v, x:Number, y:Number ) function executeInstruction(code) function fungeStep() function placePCMovieClip() ...idle and keypress funcs */ //////////////////////////////////////// // Funge 93 Standard // funge-space, 80x25 toroidal // Befunge machine state pc direction: // (0,0) ... (79,0) 3 // ... ... 2 0 -1: terminated // (0,24) ... (79,24) 1 var pc_x:Number; var pc_y:Number; var pc_dir:Number; var stringmode:Number; var stdin_block:Number; var realstack:Array = Array(); var xmax:Number = 80; var ymax:Number = 25; var grid:Array = Array(xmax*ymax); //////////////////////////////////// // Flunger states // mode 0: input mode 1: run var mode:Number = 0; var stdout_autoscroll:Number = 1; var instruction_counter:Number = 0; var strict_mode:Number = 1; // Graphical parameters for flunger var rowsize:Number = 15.55; var colsize:Number = 7.2; var playfield_xoff:Number = 24; var playfield_yoff:Number = 7; var num_stdoutlines:Number = 9; // pc trails for visibility var pc_history:Number = 25; var pc_histx:Array = Array(pc_history); var pc_histy:Array = Array(pc_history); var timermin:Number = 1; var timermax:Number = 500; /////////////////////////////////////// var allchars:String = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; allchars += "+-*/%!`><^v?_|\":\$.,# gp&~@"; //////////////////////////////// // for loading funges from the net var base_querystr = "http://jimbomania.com/code/fungeloader.php?q="; var my_lv:LoadVars = new LoadVars(); my_lv.onLoad = function(success:Boolean) { if (success) { _root.playfield_str = my_lv.fungeprog; } else { print_stderr("Error loading/parsing LoadVars."); } }; // Load a funge from the net somewhere function loadFungeFromNet(addr:String) { var querystr = _root.base_querystr+addr; var succ = _root.my_lv.load(querystr); } /////////////////////////////// // initGridFromTextbox() // Put the right values in the grid to reflect the contents of the textbox // variable is playfield_str function initGridFromTextbox() { var i:Number; // clear grid to spaces(ASCII 32) for(i=0;i=32)&&(ch<=126)){} else {ch = 63;} playfield_str +=Chr(ch); } // not the last time if(i<(ymax-1)){ playfield_str += "\r"; } } } /////////////////////////////// // initFunge // Set all execution state variables into an initial state // Funge grid is initialized to hold contents of textbox when "Execute" button is pressed function initFunge() { pc_x = 0; pc_y = 0; pc_dir = 0; stringmode = 0; stdin_block = 0; stringmode_mov._visible = 0; terminated_mov._visible = 0; runframe.waitingforinput_mov._visible = 0; runframe.stack = ""; stackdepth = 0; stackdepth_str = "Stack Depth: 0"; runframe.stdin = ""; runframe.stdout = ""; stderr = ""; vertstack = ""; realstack = Array(); placePCMovieClip(); initTrails(0); placePCMovieClip(); instruction_counter = 0; runframe.instruction_count = "Instruction Count: "+instruction_counter; runframe.stdout_lines = "0 of 0 lines"; } ////////////////////////////////////// // initTrails(int) // Initializes the proper number of movie clips for adjustable trail lengths function initTrails(x:Number) { if(x<0){x=0;} if(x>100){x=100;} // remove the old trails for(var i=0;i=xmax) {pc_x = 0;} if (pc_y>=ymax) {pc_y = 0;} } ////////////////////////////// // fungePush(byte) // push an int onto the stack // these are 32 bit longs, sometimes in ascii mode... function fungePush(byte) { realstack.push(Number(byte)); assembleStackString(); stackdepth++; } ////////////////////////////// // fungePop // pop an int from the stack // if Empty, just return 0. // Throw a warning if in strict mode function fungePop():Number { if(realstack.length==0){ if(strict_mode==1){ print_stderr("Pop empty stack @"+instruction_counter); } return 0; } var retval = realstack.pop(); stackdepth--; assembleStackString(); return retval; } ////////////////////////////// // assembleStackString // put together a line-wrapping(\r) string that displays the stacks contents function assembleStackString() { // now assemble the real stack string vertstack = ""; for (var i = 0; i=32)&&(ch<=125)){ if(ch<100){vertstack += " ";} vertstack += " == "; // display linefeeds as "\r" if(ch==10){vertstack += "\\r";} else {vertstack += String.fromCharCode(ch);} } vertstack += "\r"; } // only if auto scroll on? vertstack.scroll = vertstack.maxscroll; // update count here too stackdepth_str = "Stack Depth: "+stackdepth; } ////////////////////////////// // updateStdout // update scrolling of stdout textbox // could use a variable here to make it a little more general function updateStdout() { // only if auto scroll on? // maybe do this elssewhere - it borks the scroll //if(stdout_autoscroll==1){ // runframe.stdout.scroll = runframe.stdout.maxscroll; //} var size:Number = Math.min(1.0,num_stdoutlines/(runframe.stdout.maxscroll+num_stdoutlines)); var pos:Number = (runframe.stdout.scroll-1) / runframe.stdout.maxscroll; var maxbarheight:Number = 138.6;//runframe.stdout_scrollbar._height; var barh:Number = size*maxbarheight;//runframe.stdout_scrollbar.stdout_scrollhandle._height; runframe.stdout_scrollbar.stdout_scrollhandle._y = pos*(maxbarheight-barh); runframe.stdout_scrollbar.stdout_scrollhandle._yscale = 100*size; updateStdoutLineCount(); } // For grabbing the scroll bar function updateStdoutScroll() { var smax:Number = 0.01*(100-runframe.stdout_scrollbar.stdout_scrollhandle._yscale)*runframe.stdout_scrollbar._height; var scr:Number = runframe.stdout_scrollbar.stdout_scrollhandle._y / smax; runframe.stdout.scroll = Math.round(scr*runframe.stdout.maxscroll); updateStdoutLineCount(); } // indicate the # of lines in output function updateStdoutLineCount() { // line # indicator var outline1:Number = runframe.stdout.scroll; var outline2:Number = runframe.stdout.scroll+num_stdoutlines; var outlinenum:Number = runframe.stdout.maxscroll+num_stdoutlines; runframe.stdout_lines = outline1+"-"+outline2+" of "+outlinenum+" lines"; } function print_stderr(str:String) { stderr += str + "\r"; // auto-scroll stderr.scroll = stderr.maxscroll; } ////////////////////////////// // get int input // get a numeric value from stdin - split on space // TODO: blocking while waiting for input // TODO: throw error when input is not an int function getIntFromStdin() { // Input empty - BLOCK(if specified?) // maybe make a func out of this... //stdin_block = 0; if(runframe.stdin.length==0){ //trace("Waiting for input on stdin...."); stdin_block = 1; //fungePush(Number(0)); // unload idle func // light input flag runframe.waitingforinput_mov._visible = 1; // how do we turn this thing off now? return; } var lastidx:Number = 0; lastidx = runframe.stdin.indexOf(" "); if(lastidx == -1){ lastidx = runframe.stdin.length; } var thenum:Number = runframe.stdin.slice(0,lastidx); runframe.stdin = runframe.stdin.slice(lastidx+1,runframe.stdin.length); //trace("Num: "+thenum+" stdin: "+runframe.stdin); fungePush(Number(thenum)); } ////////////////////////////// // get char input - first char in stdin function getCharFromStdin() { if(runframe.stdin.length==0){ //trace("Waiting for input on stdin...."); stdin_block = 1; //fungePush(Number(0)); // unload idle func // light input flag runframe.waitingforinput_mov._visible = 1; // how do we turn this thing off now? // maybe a click to continue on the button that lights up.... return; } //var thenum = runframe.stdin.charAt(0); var thenum = runframe.stdin.slice(0,1); runframe.stdin = runframe.stdin.slice(1,runframe.stdin.length); //trace("Num: "+thenum+" stdin: "+runframe.stdin); fungePush(Ord(thenum)); } //////////////////////////////////////////// // getInstruction(x,y) // Obtain the byte that resides at the given location on the grid function getInstruction(x:Number, y:Number) { // BOUND CHECK var err:String = "g out @ "+instruction_counter+": ("+x+","+y+")"; if((x<0)||(x>=xmax)){ print_stderr(err); } if((y<0)||(y>=ymax)){ print_stderr(err); } return Chr(grid[y*xmax+x]); } //////////////////////////////////////////// // putInstruction(x,y) // Insert an instruction at a given (x,y) location // CRUNCHY // "g"11p"p"22p"+"33p"v"44p // "t"01p "r"11p "u"21p "s"31p function putInstruction(v, x:Number, y:Number ) { // BOUND CHECK var err:String = "p out @ "+instruction_counter+": ("+x+","+y+")"; if((x<0)||(x>=xmax)){ print_stderr(err); } if((y<0)||(y>=ymax)){ print_stderr(err); } grid[y*xmax+x] = v; initTextboxFromGrid(); } /* From BeFunge93 Specification Chris Pressey, Cat's Eye Technologies COMMAND INITIAL STACK (bot->top)RESULT (STACK) ------- ------------- ----------------- + (add) - (subtract) * (multiply) / (divide) (nb. integer) % (modulo) ! (not) <0 if value non-zero, 1 otherwise> ` (greater) <1 if value1 > value2, 0 otherwise> > (right) PC -> right < (left) PC -> left ^ (up) PC -> up v (down) PC -> down ? (random) PC -> right? left? up? down? ??? _ (horizontal if) PC->left if , else PC->right | (vertical if) PC->up if , else PC->down " (stringmode) Toggles 'stringmode' : (dup) \ (swap) $ (pop) pops but does nothing . (pop) outputs as integer , (pop) outputs as ASCII # (bridge) 'jumps' PC one farther; skips over next command g (get) p (put) puts at (x,y) & (input value) ~ (input character) @ (end) ends program FIX: add error spitting behavior when strict mode is on */ function executeInstruction(code) { var op1,op2,op3; // String mode: just push each character unless its another " if (stringmode == 1) { if (code == "\"") { stringmode = 0; stringmode_mov._visible = 0; } else {fungePush(Ord(code));} return; } // regular mode switch (code) { case " " : break; case "0" : case "1" : case "2" : case "3" : case "4" : case "5" : case "6" : case "7" : case "8" : case "9" : fungePush(code); break; // This may not be right all the time // watch out for ascii mode case "+" : fungePush(Number(fungePop())+Number(fungePop())); break; case "-" : op1 = Number(fungePop()); op2 = Number(fungePop()); fungePush(op2-op1); break; case "*" : fungePush(Number(fungePop())*Number(fungePop())); break; case "/" : op1 = Number(fungePop()); op2 = Number(fungePop()); fungePush(Math.round(op2/op1)); break; case "%" : fungePush(Number(fungePop())%Number(fungePop())); break; case "!" : if (Number(fungePop() == 0)) { fungePush(Number(1)); } else { fungePush(Number(0)); } break; case "`" : op1 = Number(fungePop()); op2 = Number(fungePop()); if (op2>op1) { fungePush(Number(1)); } else { fungePush(Number(0)); } break; case "<" : pc_dir = 2; break; case ">" : pc_dir = 0; break; case "v" : pc_dir = 1; break; case "^" : pc_dir = 3; break; case "?" : pc_dir = Math.round(Math.random()*256)%4; break; case "_" : if (Number(fungePop()) == 0) { pc_dir = 0; } else { pc_dir = 2; } break; case "|" : if (Number(fungePop()) == 0) { pc_dir = 1; } else { pc_dir = 3; } break; case "\"" : stringmode = 1-stringmode; // put up state flag stringmode_mov._visible = 1; break; case "." : runframe.stdout += fungePop(); runframe.stdout += " "; updateStdout(); if(stdout_autoscroll==1){ runframe.stdout.scroll = runframe.stdout.maxscroll; } break; case "," : runframe.stdout += String.fromCharCode(Number(fungePop())); updateStdout(); if(stdout_autoscroll==1){ runframe.stdout.scroll = runframe.stdout.maxscroll; } break; case "#" : if (pc_dir == 0) {pc_x++;} if (pc_dir == 1) {pc_y++;} if (pc_dir == 2) {pc_x--;} if (pc_dir == 3) {pc_y--;} pcBoundaryConstrain(); break; case ":" : op1 = Number(fungePop()); fungePush(op1); fungePush(op1); break; case "$" : fungePop(); break; case "\\" : op1 = Number(fungePop()); op2 = Number(fungePop()); fungePush(op1); fungePush(op2); break; case "g" : op1 = Number(fungePop()); op2 = Number(fungePop()); // what if it isnt a befunge instruction? var inst = getInstruction(op2,op1); fungePush(Ord(inst)); break; case "p" : op1 = Number(fungePop()); op2 = Number(fungePop()); op3 = Number(fungePop()); putInstruction(op3, op2, op1); break; case "&" : getIntFromStdin(); break; case "~" : getCharFromStdin(); break; case "@" : // put up state flag //trace("PROGRAM TERMINATED"); pc_dir = -1; terminated_mov._visible = 1; break; default : if(strict_mode==1) { print_stderr("Bad instruction @"+instruction_counter+": ["+code+"]"); } break; } } /////////////////////////////////////////// // Main Iterator function here // Take the instruction under the pc and execute it, // then move the pc by its direction function fungeStep() { if (mode == 0) {return;} // not when terminated if (pc_dir == -1) {return;} // if blocking on stdin, do not execute but continue to wait // check to see if the input has come in? if (stdin_block == 1) { return; } // store last position in history // shuffle if history full pc_histx.unshift(pc_x); pc_histy.unshift(pc_y); if(pc_histx.length>=pc_history){ pc_histx.pop(); pc_histy.pop(); } // read byte and execute command var curbyte = getInstruction(pc_x, pc_y); executeInstruction(curbyte); // step the pc switch (pc_dir) { case 0 : // right pc_x++; break; case 1 : // down pc_y++; break; case 2 : // left pc_x--; break; case 3 : // up pc_y--; break; // terminated - do nothing case -1 : break; default : print_stderr("Bad direction: "+pc_dir); break; } pcBoundaryConstrain(); placePCMovieClip(); instruction_counter++; runframe.instruction_count = "Instruction Count: "+instruction_counter; } //////////////// End Funge vm ////////////////////// //////////////// Flash-specific funcs follow ////////////////////// function placePCMovieClip(){ var trailalphamax:Number = 2; // assign location to pc movieclip pc._x = playfield_xoff+colsize*pc_x; pc._y = playfield_yoff+rowsize*pc_y; // place the trails too // tweak the trailer mov var txo:Number = 4; var tyo:Number = 4; for(var i=0;i0.05){ executeidlefunc = setInterval(fungeStep, timerval); runframe.speedhandle.speedbar_text = Math.min(100,Math.round(100*pct)); } else {runframe.speedhandle.speedbar_text ="Off";} } // trails bar - set the length of the execution trails var trailsbarmin:Number = runframe.trailsbar._y; var trailsbarmax:Number = runframe.trailsbar._y+runframe.trailsbar._height-runframe.trailshandle._height; function trailsBarIdle() { var pct:Number = (runframe.trailshandle._y-trailsbarmin)/(trailsbarmax-trailsbarmin); pct = (1-pct); runframe.trailshandle.trailsbar_text = Math.min(100,Math.round(100*pct)); initTrails( Math.min(100,Math.round(100*pct)) ); } /////////////////////////////// // myOnKeyDown // Keyboard press detection function myOnKeyDown() { switch (Key.getCode()) { case 96 : // pad0 fungeStep(); break; case 98 : // pad2 dumpGrid(); break; case 32 : case 27 : case 97 : // space, escape, pad1 //trace("FULL STOP"); clearInterval(idlefunc); clearInterval(trails_idlefunc); clearInterval(executeidlefunc); clearInterval(stdoutscroll_idlefunc); runframe.stdout_scrollbar.stdout_scrollhandle.stopDrag(); break; default : break; } } ////////////////////////////////// // // Execution Begins here // var myListener:Object = new Object(); myListener.onKeyDown = myOnKeyDown; Key.addListener(myListener); fieldblocker._visible = 0; runframe._visible = 0; pc._visible = 0; initFunge(); //playfield_str = "";