program mandelbrot2;
{Second Mandelbrot Set drawer. This program extends the previous
one by including a limited zoom mode.}

uses crt; {A unit that allows us to read single keypresses.}

{Note to C programmers: the variable type "real" is equivalent
to the C type "float".}

var cx,cy: real; {Where do we want to center the brot?}
    scale: real; {This is the "zoom" factor.}
    limit: word; {Divergence check value.}
    lp: word; {Convergence check value.}
    a1,b1,a2,b2: real; {For calculating the iterations.}
    x,y: integer; {The pixel we are drawing.}
    ax,ay: real; {The actual position of (x,y) in relation to
                  the Mandelbrot set.}
    key: char; {Dummy value for keypresses.}

procedure init256;
{Initialises a VGA mode 320x200 pixels, 256 colours. A little chunky
but the extra colours are worth it, and SVGA is beyond the scope of
this program.}
begin
    asm
      mov ah,0
      mov al,$13
      int $10
    end;
    {This is a little direct assembly language. Feel free to use this
    routine, and the others that follow, in your own code.}
end;

procedure end256;
{Turns off the video mode and returns to standard 16-colour text mode.}
begin
    asm
      mov ah,0
      mov al,$03
      int $10
    end;
end;

procedure pixel(a,b: word; c: byte);
{This procedure plots a pixel at screen coordinate (a,b) in colour c.
Again, there is no need for you to understand the intrinsic workings
of this routine - you are welcome to use it in your programs but do
be aware that silly values for a and b could cause unexpected results.}
var v: word;
begin
    v:=a+b*320;
    mem[$A000:v]:=c;
end;

procedure draw_brot;
begin
    {Loop through all pixels on screen. For reasons that will become
    clear, I am counting not from (0,0) but from (-160,-100).}
    for x:=-160 to 159 do
      for y:=-100 to 100 do begin
        {What is the *mathematical* value of this point?}
        ax:=cx+x*scale; ay:=cy+y*scale;

        {And now for the magic formula!}
        a1:=ax; b1:=ay; lp:=0;
        repeat
          {Do one iteration.}
          lp:=lp+1;
          a2:=a1*a1-b1*b1+ax;
          b2:=2*a1*b1+ay;
          {This is indeed the square of a+bi, done component-wise.}
          a1:=a2; b1:=b2;
        until (lp>255) or ((a1*a1)+(b1*b1)>limit);
        {The first condition is satisfied if we have convergence.
        The second is satisfied if we have divergence.}

        {Define colour and draw pixel.}
        if lp>255 then lp:=0;
        {If the point converges, it is part of the brot and we
        draw it with colour 0, or black.}
        pixel(x+160,y+100,lp);
      end;
end;

function getpix(x,y: integer): integer;
{Function that returns the colour of pixel (x,y). Basically
the Pixel procedure in reverse.}
var q: word;
begin
    q:=x+y*320;
    getpix:=Mem[$A000:q];
end;

procedure draw_cross(x,y: word);
{Draws a crosshair centered on (x,y), using XOR plotting.
See article for details!}
var i: integer;
    j: byte;
begin
    {Vertical line...}
    for i:=-5 to 5 do begin
        j:=getpix(x,y+i) xor 255;
        pixel(x,y+i,j);
    end;
    {And horizontal line...}
    for i:=-5 to 5 do begin
        j:=getpix(x+i,y) xor 255;
        pixel(x+i,y,j);
    end;
end;

procedure select_point;
{A procedure which lets the user select a place to zoom in on.}
var xcross,ycross: integer;
{These are the x and y coordinates of our selecting crosshair.}
begin
    {Put crosshair in middle of screen.}
    xcross:=160; ycross:=100; draw_cross(xcross,ycross);
    repeat
      key:=readkey;
      draw_cross(xcross,ycross);
      {A neat thing about XOR plotting is that if you do it
      twice, the second time restores the old colours!}
      if (key='z') or (key='Z') then begin
        xcross:=xcross-1;
        if xcross<0 then xcross:=0;
      end;
      {Prevents the cross being moved off the display.
      The same trick applies to the next three.}
      if (key='x') or (key='X') then begin
        xcross:=xcross+1;
        if xcross>319 then xcross:=319;
      end;
      if (key='k') or (key='K') then begin
        ycross:=ycross-1;
        if ycross<0 then ycross:=0;
      end;
      if (key='m') or (key='M') then begin
        ycross:=ycross+1;
        if ycross>199 then ycross:=199;
      end;
      draw_cross(xcross,ycross);
      {Redraw cross, in new location.}
    until key=' ';
    {When spacebar is pressed, start the zoom.}
    cx:=cx+scale*(xcross-160);
    cy:=cy+scale*(ycross-100);
    scale:=scale/2;
    {Shifts cx,cy accordingly and doubles the zoom level.
    The picture is redrawn outside this procedure.}
end;

begin {Main section!}
    {Set up video mode.}
    init256;

    {Set up initial values for drawing. Try compiling the program
    with different values here if you like!}
    cx:=0; cy:=0; scale:=0.02;
    limit:=4;
    draw_brot;
    {Draws Mandelbrot set with initial values.}

    repeat
      key:=readkey;
      {Takes a keypress from user.}
      if key=' ' then begin
        select_point;
        draw_brot;
      end;
      {If spacebar pressed, user wishes to zoom in.}
    until (key='Q') or (key='q');
    {If Q pressed, user wishes to leave program.}

    end256;
    {Return to text mode.}
end.