This code prints a Mandelbrot set in ASCII art to terminal.

First of all, some variables are declared. The initial maximum number of iterations is set to 128.

~~~'x var   'y var    'iter var     #128 'max-iter var-n   #1 'zoom var-n #0 'posx var-n #0 'posy var-n ~~~

We are using fixed-point numbers with a scaling factor of 10'000. That means that the float value 1.0 is represented by the number 10'000. For that, we need a special multiplication definition.

~~~:f* * #10000 / ;   ~~~

Now the calculation of the Mandelbrot set value at a specified point is defined.

~~~:mb:value (x_y--v)     #0  !x     #0  !y     #-1 !iter     [        @iter #1 + !iter             dup-pair        #2 @x * @y f* + swap (new_y)       @x @x f* @y @y f* - +  (new_x)       !x !y          @x @x f* @y @y f* + #40000 lteq?        @iter @max-iter lt? and      ] while     drop drop @iter   ;   ~~~

In order to display the mandelbrot set nicely, 10 different iteration levels are filled with their equivalent ASCII signs.

~~~:ascii-equiv (n--c)   #9 * @max-iter /     #0 [ #32 ] case   #1 [ \$. ] case   #2 [ \$: ] case   #3 [ \$- ] case   #4 [ \$= ] case   #5 [ \$+ ] case   #6 [ \$* ] case   #7 [ \$# ] case   #8 [ \$% ] case   #9 [ \$@ ] case ; ~~~

The user can change some parameters by entering key press sequences. The following code checks for those key presses...

~~~:zoom+? dup \$+ eq? [ @zoom #2 * !zoom ] if ;   :zoom-? dup \$- eq? [ @zoom #2 / #1 n:max !zoom ] if ;   :up?    dup \$w eq? [ @posy #10000 @zoom / - !posy ] if ;   :down?  dup \$s eq? [ @posy #10000 @zoom / + !posy ]  if ;   :left?  dup \$a eq? [ @posx #10000 @zoom / - !posx ]  if ;   :right? dup \$d eq? [ @posx #10000 @zoom / + !posx ]  if ;   :resinc? dup \$e eq? [ @max-iter #2 * !max-iter ] if ;   :resdec? dup \$q eq? [ @max-iter #2 / #1 n:max !max-iter ]  if ;   ~~~

...and this word bundles all those together.

~~~:input:handle   c:get   zoom+? zoom-?   up? down?   left? right?   resinc? resdec?   drop   nl ; ~~~

A quick function draws some information about key bindings and the current zoom level, as well as the current maximum number of iterations.

~~~:info:draw   '+/-_to_zoom;_w/a/s/d_to_move s:put nl     'q/e_to_increase_decrease_resolution s:put nl     'zoom_level_ s:put @zoom n:put nl     'iterations_ s:put @max-iter n:put nl    ; ~~~

Finally the actual drawing code is written. It renders to 25x80 characters. The width of 80 characters is distributed over a set width of 3.5 (35000) units, the height of 25 characters is distributed over a set height of 2 units. This results in a scaling factor of 438 for the width and 800 for the height.

~~~:mb:draw     #25 [     #80 [         I #438 * #25000 - @zoom / @posx +         J #800 * #10000 - @zoom / @posy +         mb:value         ascii-equiv c:put       ] indexed-times     nl     ] indexed-times  ;   ~~~

Before we start, the terminal is set to character-buffered. This makes it possible to react to key presses directly, instead of only after the enter key is pressed. The last function is a loop that draws the set, some info and gets key- presses.

~~~'stty_cbreak unix:system   [     mb:draw     info:draw   input:handle TRUE ] while   ~~~