retroforth/example/mandelbrot.retro
crc 2e95ec501a update more examples to use new word names
FossilOrigin-Name: 708314f58b4253b273c217a11265725bad4c8d7d7c53c503510440dade50be4d
2020-07-06 02:49:49 +00:00

145 lines
3.1 KiB
Forth

Copyright 2019 jmf
License: WTFPL
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
~~~