Mandelbrot Program

General discussions related to the Altair 8800 Clone

Mandelbrot Program

Postby Wayne Parham » July 18th, 2022, 10:17 pm

Here's another fun little C concoction. And I do mean concoction - it's pretty hackish. But it runs, and it's fun. It's probably pretty much like the very first Mandelbrot rendering programs were, being limited in both processing power and graphics resolution or even display options.

The Mandelbrot set "lives" in a two-dimensional grid between -2 and +1 on the x-axis, and between 1 and -1 on the y-axis, which is really plotting imaginary numbers, so between i and -i. So it lives in a pretty small space, with almost all of its coordinates being fractional values less than one. Plus it's computationally exhaustive, so a hardware floating point unit would be nice.

BDS C doesn't support floating point types, which is OK because using them probably would suck without a math co-processor anyway. So we can shift decimal places and use fixed-point math. But even that is limited, because BDS C doesn't support long types either.

However, there are some libraries that allow the use of structures to do floating-point and long integer math. They don't supply the types, but they do allow conversion using byte values cast as chars. As I said, it's hackish.

I chose the long hack, 'cause I think it's best suited to an 8-bit processor. And the output is in ASCII, making an image with characters. It's actually displaying the set by showing the number of iterations required to rule-out a value of the set, making the image shown as a negative.

Hope you enjoy it.

To build, you'll need my program, Shostak's long library and the make file. Copy-and-paste each of these three files onto your BDS C disk and build using this command:

SUBMIT MAKE MANDELBR

Mandelbr.c:
Code: Select all
/* Mandelbr.c                                    */
/*                                               */
/* Generates ASCII output of the Mandelbrot set  */
/* using  (long)  integer math from the  BDS  C  */
/* library  that  represents longs  as  char[4]  */
/*                                               */
/* Written by Wayne Parham                       */


#include <stdio.h>


#define DISPCOLS        80
#define DISPROWS        23
#define INIT_LEFT     -560  /* (x size 800, 80 steps) */
#define INIT_RIGHT     240
#define INIT_TOP       220  /* (y size 460, 23 steps) */
#define INIT_BOTTOM   -240
#define INIT_X_INCR     10  /* (step ratio 2:1, y:x ) */
#define INIT_Y_INCR     20
#define INIT_MAX_ITER   50

char     disbuf[DISPCOLS * DISPROWS];


void mandelbrot( boundL, boundR, boundT, boundB,
                 x_incr, y_incr, max, disp )
                 char boundL[4];
                 char boundR[4];
                 char boundT[4];
                 char boundB[4];
                 char x_incr[4];
                 char y_incr[4];
                 char    max[4];
                 int       disp;
{
   char c[4];
   char n[4];
   char cx[4];
   char cy[4];
   char zx[4];
   char zy[4];
   char zxx[4];
   char zyy[4];
   char zxzy[4];
   char zxxpluszyy[4];
   char zxxminuszyy[4];
   char p;
   int  i;
                                /* for( cy=boundT; cy > boundB; cy -= y_incr ) {    */
   i = 0;                       /*    for( cx=boundL; cx < boundR; cx += x_incr ) { */

   for( lassign( cy, boundT );  lcomp( cy, boundB ) == 1;  lsub( cy, cy, y_incr ) ) {
      for( lassign( cx, boundL );  lcomp( cx, boundR) == -1;  ladd( cx, cx, x_incr ) ) {

         itol(  n, 0 );                               /* n  = 0;                    */
         itol( zy, 0 );                               /* zy = 0;                    */
         itol( zx, 0 );                               /* zx = 0;                    */

         p  = ' ';

    while( lcomp( n, max ) == -1 ) {             /* while( n < max ) {         */

            itol( c, 200 );

            lmul( zxx, zx, zx );
            ldiv( zxx, zxx, c );                      /* zxx = zx * zx / 200;       */

            lmul( zyy, zy, zy );
            ldiv( zyy, zyy, c );                      /* zyy = zy * zy / 200;       */

            itol( c, 800 );
            ladd( zxxpluszyy, zxx, zyy );

            if( lcomp( zxxpluszyy, c ) == 1 ) {       /* if( (zxx + zyy) > 800 ) {  */

               itol( c, 9 );
               if( lcomp( n, c ) == 1 ) {             /* if( n > 9 ) {              */
                  p = '0';                            /*    (char) p = '0'          */   
               } else {                               /* } else {                   */
                  p = ltou(n) + 48;                   /*    (char) p = n + 48       */
               }                                      /* }    (an ASCII conversion) */

               lassign(  n, max );                    /* n = max;                   */
            }

            itol( c, 100 );
            lmul( zxzy, zx, zy );
            ldiv( zxzy, zxzy, c);                     /* zy = zx * zy / 100 + cy;   */
            ladd( zy, zxzy, cy );

            lsub( zxxminuszyy, zxx, zyy );
            ladd( zx, zxxminuszyy, cx );              /* zx = zxx - zyy + cx;       */

            itol( c, 1 );
            ladd( n, n, c );                          /* n++;                       */
         }

         disbuf[i] = p;
    i++;
         if( disp )
            putchar( p );
      }
      if( disp )
         putchar( '\n' );
   }
}


void display() {
   int i,x,y;
   i = 0;

   putchar( '\n' );
   for( y=0; y < DISPROWS; y++ ) {
      for( x=0; x < DISPCOLS; x++ ) {
         putchar( disbuf[i] );
         i++;
      }
      putchar( '\n' );
   }
}


char getcmd() {
   char cmd;
   cmd = getchar();
   putchar( 0x08 );
   switch( cmd ) {
      case 'A' :
      case 'a' :
         puts( "Entering auto mode." );
         break;
      case 'L' :
      case 'l' :
         puts( "Moving left and re-rendering." );
         break;
      case 'R' :
      case 'r' :
         puts( "Moving right and re-rendering." );
         break;
      case 'U' :
      case 'u' :
         puts( "Moving up and re-rendering." );
         break;
      case 'D' :
      case 'd' :
         puts( "Moving down and re-rendering." );
         break;
      case '.' :
         puts( "Re-centering and re-rendering." );
         break;
      case '+' :
         puts( "Zooming in and re-rendering." );
         break;
      case '-' :
         puts( "Zooming out and re-rendering." );
         break;
      case 'X' :
      case 'x' :
         puts( "Enjoy your day!" );
         break;
      default  :
         break;
   }
   return( cmd );
}


void autorender( boundL, boundR, boundT, boundB, x_incr, y_incr, max )
                 char boundL[4];
                 char boundR[4];
                 char boundT[4];
                 char boundB[4];
                 char x_incr[4];
                 char y_incr[4];
                 char    max[4];
{
   int i, move, setnum, setratio, plot_points;
   char maxdist[4];
   char twf[4];
   char ten[4];
   char five[4];
   char two[4];
   char one[4];

   itol( maxdist, 1280 );
   itol( twf, 25 );
   itol( ten, 10 );
   itol( five, 5 );
   itol( two, 2 );
   itol( one, 1 );

   plot_points = DISPCOLS * DISPROWS;

   while( 1 ) {
      for( i=0; i < plot_points; i++ ) {
         if( disbuf[i] == ' ' ) {
            setnum++;
         }
      }
      setratio = 100 * setnum / plot_points;
      if( (setratio > 90) || (setratio < 10) ) {
         itol( boundL, INIT_LEFT );
         itol( boundR, INIT_RIGHT );
         itol( boundT, INIT_TOP );
         itol( boundB, INIT_BOTTOM );
         itol( x_incr, INIT_X_INCR );
         itol( y_incr, INIT_Y_INCR );
         itol( max,    INIT_MAX_ITER );
      } else {
         move = rand() % 6;
         switch( move ) {
            case 0 :
               if( lcomp( x_incr, one ) == 1 ) {      /* if( x_incr > 1 ) {               */
                  if( lcomp( x_incr, five ) != 0 ) {  /*    if( x_incr != 5 ) {           */
                     ldiv( x_incr, x_incr, two );     /*       x_incr = x_incr / 2;       */
                     ldiv( y_incr, y_incr, two );     /*       y_incr = y_incr / 2;       */
                     ldiv( boundL, boundL, two );     /*       boundL = boundL / 2;       */
                     ldiv( boundR, boundR, two );     /*       boundR = boundR / 2;       */
                     ldiv( boundT, boundT, two );     /*       boundT = boundT / 2;       */
                     ldiv( boundB, boundB, two );     /*       boundB = boundB / 2;       */
                  } else {                            /*    } else {                      */
                     lmul( x_incr, x_incr, ten );     /*                                  */
                     ldiv( x_incr, x_incr, twf );     /*       x_incr = x_incr * 10 / 25; */
                     lmul( y_incr, y_incr, ten );     /*                                  */
                     ldiv( y_incr, y_incr, twf );     /*       y_incr = y_incr * 10 / 25; */
                     lmul( boundL, boundL, ten );     /*                                  */
                     ldiv( boundL, boundL, twf );     /*       boundL = boundL * 10 / 25; */
                     lmul( boundR, boundR, ten );     /*                                  */
                     ldiv( boundR, boundR, twf );     /*       boundR = boundR * 10 / 25; */
                     lmul( boundT, boundT, ten );     /*                                  */
                     ldiv( boundT, boundT, twf );     /*       boundT = boundT * 10 / 25; */
                     lmul( boundB, boundB, ten );     /*                                  */
                     ldiv( boundB, boundB, twf );     /*       boundB = boundB * 10 / 25; */
                  }
               }
               break;
            case 1 :
               if( lcomp( x_incr, maxdist ) == -1 ) { /* if( x_incr < 1280 ) {            */
                  if( lcomp( x_incr, two ) != 0 ) {   /*    if( x_incr != 2 ) {           */
                     lmul( x_incr, x_incr, two );     /*       x_incr = x_incr * 2;       */
                     lmul( y_incr, y_incr, two );     /*       y_incr = y_incr * 2;       */
                     lmul( boundL, boundL, two );     /*       boundL = boundL * 2;       */
                     lmul( boundR, boundR, two );     /*       boundR = boundR * 2;       */
                     lmul( boundT, boundT, two );     /*       boundT = boundT * 2;       */
                     lmul( boundB, boundB, two );     /*       boundB = boundB * 2;       */
                  } else {                            /*    } else {                      */
                     lmul( x_incr, x_incr, twf );     /*                                  */
                     ldiv( x_incr, x_incr, ten );     /*       x_incr = x_incr * 25 / 10; */
                     lmul( y_incr, y_incr, twf );     /*                                  */
                     ldiv( y_incr, y_incr, ten );     /*       y_incr = y_incr * 25 / 10; */
                     lmul( boundL, boundL, twf );     /*                                  */
                     ldiv( boundL, boundL, ten );     /*       boundL = boundL * 25 / 10; */
                     lmul( boundR, boundR, twf );     /*                                  */
                     ldiv( boundR, boundR, ten );     /*       boundR = boundR * 25 / 10; */
                     lmul( boundT, boundT, twf );     /*                                  */
                     ldiv( boundT, boundT, ten );     /*       boundT = boundT * 25 / 10; */
                     lmul( boundB, boundB, twf );     /*                                  */
                     ldiv( boundB, boundB, ten );     /*       boundB = boundB * 25 / 10; */
                  }
               }
               break;
            case 2 :
               lsub( boundL, boundL,  x_incr );       /* boundL = boundL - x_incr;        */
               lsub( boundR, boundR,  x_incr );       /* boundR = boundR - x_incr;        */
               break;
            case 3 :
               ladd( boundL, boundL,  x_incr );       /* boundL = boundL + x_incr;        */
               ladd( boundR, boundR,  x_incr );       /* boundR = boundR + x_incr;        */
               break;
            case 4 :
               ladd( boundT, boundT,  y_incr );       /* boundT = boundT + y_incr;        */
               ladd( boundB, boundB,  y_incr );       /* boundB = boundB + y_incr;        */
               break;
            case 5 :
               lsub( boundT, boundT,  y_incr );       /* boundT = boundT - y_incr;        */
               lsub( boundB, boundB,  y_incr );       /* boundB = boundB - y_incr;        */
               break;
         }
      }

      mandelbrot( boundL, boundR, boundT, boundB, x_incr, y_incr, max, 0 );
      display();
   }
}


void instructions() {
   puts( "\n" );
   puts( "The Mandelbrot set includes all two-dimensional c values for which the function\n" );
   puts( "z = z^2 + c does not diverge when iterated from z = 0.  It was named in tribute\n" );
   puts( "to the mathematician Benoit Mandelbrot, a pioneer of fractal geometry.   He was\n" );
   puts( "first  to see a visualization of the set on March 1, 1980, at IBM's  Thomas  J.\n" );
   puts( "Watson Research Center in Yorktown Heights, New York.\n\n" );
   puts( "The  Mandelbrot  set  has  its  origin  in  complex  dynamics,  a  field  first\n" );
   puts( "investigated by the French mathematicians Pierre Fatou and Gaston Julia at  the\n" );
   puts( "beginning of the 20th century. This fractal was first defined and drawn in 1978\n" );
   puts( "by Robert W. Brooks and Peter Matelski as part of a study of Kleinian groups.\n\n" );
   puts( "Mandelbrot  studied the parameter space of quadratic polynomials in an  article\n" );
   puts( "that  appeared  in 1980.  The mathematical study of the Mandelbrot  set  really\n" );
   puts( "began with work by the mathematicians Adrien Douady and John H. Hubbard (1985),\n" );
   puts( "who  established many of its fundamental properties and named the set in  honor\n" );
   puts( "of Mandelbrot for his influential work in fractal geometry.\n\n" );
   puts( "This  program  displays the set on an ASCII terminal, much  like  the  earliest\n" );
   puts( "computers did.  It is a throwback to the computing technologies of  yesteryear.\n" );
   puts( "It will display the set and then allow you to zoom or scroll. You can zoom with\n" );
   puts( "'+'  or '-' or move in any direction with 'u', 'd', 'l' or 'r'.   Enter '.'  to\n" );
   puts( "reset the view.  Set auto mode with the 'a' key.\n\n" );

   return;
}


void main() {
   int   changed;
   int   running;
   char  maxdist[4];
   char  twf[4];
   char  ten[4];
   char  five[4];
   char  two[4];
   char  one[4];
   char  boundL[4];
   char  boundR[4];
   char  boundT[4];
   char  boundB[4];
   char  x_incr[4];
   char  y_incr[4];
   char  max[4];
   char* disbuf;
   char  cmd;

   itol( maxdist, 1280 );
   itol( twf, 25 );
   itol( ten, 10 );
   itol( five, 5 );
   itol( two, 2 );
   itol( one, 1 );

   itol( boundL, INIT_LEFT );
   itol( boundR, INIT_RIGHT );
   itol( boundT, INIT_TOP );
   itol( boundB, INIT_BOTTOM );
   itol( x_incr, INIT_X_INCR );
   itol( y_incr, INIT_Y_INCR );
   itol( max,    INIT_MAX_ITER );

   instructions();
   srand();
     
   mandelbrot( boundL, boundR, boundT, boundB, x_incr, y_incr, max, 1 );
     
   running = 1;
   while( running ) {
      cmd = getcmd();
      changed = 1;

      switch( cmd ) {
         case 'A' :
         case 'a' :
            autorender( boundL, boundR, boundT, boundB, x_incr, y_incr, max );
            break;

         case 'X' :
         case 'x' :
            running = 0;
            break;

         case 'L' :
         case 'l' :
            lsub( boundL, boundL,  x_incr );          /* boundL = boundL - x_incr;        */
            lsub( boundR, boundR,  x_incr );          /* boundR = boundR - x_incr;        */
            break;

         case 'R' :
         case 'r' :
            ladd( boundL, boundL,  x_incr );          /* boundL = boundL + x_incr;        */
            ladd( boundR, boundR,  x_incr );          /* boundR = boundR + x_incr;        */
            break;

         case 'U' :
         case 'u' :
            ladd( boundT, boundT,  y_incr );          /* boundT = boundT + y_incr;        */
            ladd( boundB, boundB,  y_incr );          /* boundB = boundB + y_incr;        */
            break;

         case 'D' :
         case 'd' :
            lsub( boundT, boundT,  y_incr );          /* boundT = boundT - y_incr;        */
            lsub( boundB, boundB,  y_incr );          /* boundB = boundB - y_incr;        */
            break;

         case '.' :
            itol( boundL, INIT_LEFT );
            itol( boundR, INIT_RIGHT );
            itol( boundT, INIT_TOP );
            itol( boundB, INIT_BOTTOM );
            itol( x_incr, INIT_X_INCR );
            itol( y_incr, INIT_Y_INCR );
            break;

         case '+' :
            if( lcomp( x_incr, one ) == 1 ) {         /* if( x_incr > 1 ) {               */
               if( lcomp( x_incr, five ) != 0 ) {     /*    if( x_incr != 5 ) {           */
                  ldiv( x_incr, x_incr, two );        /*       x_incr = x_incr / 2;       */
                  ldiv( y_incr, y_incr, two );        /*       y_incr = y_incr / 2;       */
                  ldiv( boundL, boundL, two );        /*       boundL = boundL / 2;       */
                  ldiv( boundR, boundR, two );        /*       boundR = boundR / 2;       */
                  ldiv( boundT, boundT, two );        /*       boundT = boundT / 2;       */
                  ldiv( boundB, boundB, two );        /*       boundB = boundB / 2;       */
               } else {                               /*    } else {                      */
                  lmul( x_incr, x_incr, ten );        /*                                  */
                  ldiv( x_incr, x_incr, twf );        /*       x_incr = x_incr * 10 / 25; */
                  lmul( y_incr, y_incr, ten );        /*                                  */
                  ldiv( y_incr, y_incr, twf );        /*       y_incr = y_incr * 10 / 25; */
                  lmul( boundL, boundL, ten );        /*                                  */
                  ldiv( boundL, boundL, twf );        /*       boundL = boundL * 10 / 25; */
                  lmul( boundR, boundR, ten );        /*                                  */
                  ldiv( boundR, boundR, twf );        /*       boundR = boundR * 10 / 25; */
                  lmul( boundT, boundT, ten );        /*                                  */
                  ldiv( boundT, boundT, twf );        /*       boundT = boundT * 10 / 25; */
                  lmul( boundB, boundB, ten );        /*                                  */
                  ldiv( boundB, boundB, twf );        /*       boundB = boundB * 10 / 25; */
               }
            } else {
               printf ( "\rMaximum zoom reached.       " );
            }
            break;

         case '-' :
            if( lcomp( x_incr, maxdist ) == -1 ) {    /* if( x_incr < 1280 ) {            */
               if( lcomp( x_incr, two ) != 0 ) {      /*    if( x_incr != 2 ) {           */
                  lmul( x_incr, x_incr, two );        /*       x_incr = x_incr * 2;       */
                  lmul( y_incr, y_incr, two );        /*       y_incr = y_incr * 2;       */
                  lmul( boundL, boundL, two );        /*       boundL = boundL * 2;       */
                  lmul( boundR, boundR, two );        /*       boundR = boundR * 2;       */
                  lmul( boundT, boundT, two );        /*       boundT = boundT * 2;       */
                  lmul( boundB, boundB, two );        /*       boundB = boundB * 2;       */
               } else {                               /*    } else {                      */
                  lmul( x_incr, x_incr, twf );        /*                                  */
                  ldiv( x_incr, x_incr, ten );        /*       x_incr = x_incr * 25 / 10; */
                  lmul( y_incr, y_incr, twf );        /*                                  */
                  ldiv( y_incr, y_incr, ten );        /*       y_incr = y_incr * 25 / 10; */
                  lmul( boundL, boundL, twf );        /*                                  */
                  ldiv( boundL, boundL, ten );        /*       boundL = boundL * 25 / 10; */
                  lmul( boundR, boundR, twf );        /*                                  */
                  ldiv( boundR, boundR, ten );        /*       boundR = boundR * 25 / 10; */
                  lmul( boundT, boundT, twf );        /*                                  */
                  ldiv( boundT, boundT, ten );        /*       boundT = boundT * 25 / 10; */
                  lmul( boundB, boundB, twf );        /*                                  */
                  ldiv( boundB, boundB, ten );        /*       boundB = boundB * 25 / 10; */
               }
            } else {
               printf ( "\rMaximum distance reached.    " );
            }
            break;

         default  :
            changed = 0;
            break;
      }

      if( running && changed ) {
          mandelbrot( boundL, boundR, boundT, boundB, x_incr, y_incr, max, 0 );
          display();
      }
   }

   return;
}
Wayne Parham
 
Posts: 240
Joined: March 18th, 2022, 3:01 pm

Re: Mandelbrot Program

Postby Wayne Parham » July 18th, 2022, 10:18 pm

long.c:
Code: Select all
/*   C-Source for Long (32-bit signed) Integer Package
 *   Rob Shostak
 *   August, 1982         
 */
       


char *itol(result,n)         /* integer to long */
char *result;
int n;
{
return(long(0,result,n));
}

int ltoi(l)            /* converts long to integer */
char l[];
{
return(l[3] + (l[2] << 8));
}

lcomp(op1,op2)            /* compares two longs */
char *op1,*op2;
{
return(long(1,op1,op2));
}

char *ladd(result,op1,op2)      /* adds two longs */
char *result,*op1,*op2;
{
return(long(2,result,op1,op2));
}

char *lsub(result,op1,op2)      /* subtracts two longs */
char *result,*op1,*op2;
{
return(long(3,result,op1,op2));
}

char *lmul(result,op1,op2)      /* multiplies two longs */
char *result,*op1,*op2;
{
return(long(4,result,op1,op2));
}

char *ldiv(result,op1,op2)      /* "long" division */
char *result,*op1,*op2;
{
return(long(5,result,op1,op2));
}

char *lmod(result,op1,op2)      /* long multiplication */
char *result,*op1,*op2;
{
return(long(6,result,op1,op2));
}


char *atol(result,s)         /* ascii to long */
char *result,*s;
{
char ten[4], temp[4], sign;

itol(ten,10); itol(result,0);

if(sign=(*s == '-')) s++;

while(isdigit(*s))
  ladd(result,lmul(result,result,ten),itol(temp,*s++ - '0'));

return(sign? lsub(result,itol(temp,0),result) : result);
}

char *ltoa(result,op1)            /* long to ascii string */
char *result,*op1;
{
char absval[4], temp[4];
char work[15], *workptr, sign;
char ten[4], zero[4], *rptr;

rptr = result;
itol(ten,10); itol(zero,0);         /* init these */

if(sign = *op1 & 0x80)            /* negative operand */
  {
  *rptr++ = '-';
  lsub(absval,zero,op1);
  }
else lassign(absval,op1);

*(workptr = work+14) = '\0';      /* generate digits in reverse order */
do               
  *(--workptr) = '0'+ *(lmod(temp,absval,ten) + 3);
while
  (lcomp(ldiv(absval,absval,ten),zero) > 0);

strcpy(rptr,workptr);
return(result);
}


/* lassign(ldest,lsource) assigns long lsource to ldest, returns
   ptr to ldest */

char *lassign(ldest,lsource)
unsigned *ldest, *lsource;
{
*ldest++ = *lsource++;   /* copy first two bytes */
*ldest = *lsource;   /* then last two */
return(ldest);
}

/* ltou(l) converts long l to an unsigned (by truncating) */

unsigned ltou(l)
char l[];
{
return(l[3] + (l[2] << 8));
}

/* utol(l,u) converts unsigned to long */

utol(l,u)
char l[];
unsigned u;
{
itol(l, u & 0x7FFF);         /* convert integer part */
if(u > 0x7FFF) l[2] += 0x80;      /* take care of leftmost bit */
return(l);
}
Wayne Parham
 
Posts: 240
Joined: March 18th, 2022, 3:01 pm

Re: Mandelbrot Program

Postby Wayne Parham » July 18th, 2022, 10:20 pm

make.sub:
Code: Select all
cc $1
cc long
clink $1 long
Wayne Parham
 
Posts: 240
Joined: March 18th, 2022, 3:01 pm

Re: Mandelbrot Program

Postby Wayne Parham » August 10th, 2022, 10:42 am

Quick note:

I have been running processor-intensive programs on my Altairs and Syms, just for fun. I like watchin 'em throw stuff up on the screen while doing my regular "day job."

One thing I noticed is this version of Mandelbrot is noticeably slower than the ANSI C version compiled on CC65 for the Sym-1.

I think it's probably that weirdo "long library" used in BDS C. By "weirdo" - I'm not casting aspersions and am actually saying it fondly, like someone would admire their "weirdo" flathead Ford. It's cool that the guys that made BDS C even brought this solution into play.

But I must admit that it's slow. Might be fun to port it over to Aztec and see how much faster it runs. Aztec C supports long datatypes directly, and even supports floats and doubles.

The Mandelbrot program here uses integer math, as did the ANSI C version I back-ported from. The whole point was to get something to run on an old 8-bit (integer) processor and spit out an "image" made with ASCII characters. It was definitely an exercise in doing something 1970s-style, just like long hair and bell bottoms. But I do think probably we could see a little bit of speed improvement on a different compiler.

Maybe I'll do that and report back.
Wayne Parham
 
Posts: 240
Joined: March 18th, 2022, 3:01 pm

Re: Mandelbrot Program

Postby Wayne Parham » August 10th, 2022, 3:47 pm

It was soooo much easier to back-port Mandelbr.c from ANSI to Aztec C. All I really had to do was to change the function declarations to K&R style and a few other little things and it was done.

Sure wish I had started with Aztec C! Then again, if I had done that I would have never experienced BDS C. So I'm glad I didn't know about Aztec C when I first started piddling with BDS C.

Grab a copy of Aztec C and copy the two files below onto it. Build it with this command:

SUBMIT MAKE MANDLEBR

make.sub:
Code: Select all
cc $1 -z1500
as -ZAP $1
ln $1.o c.lib


Mandelbr.c:
Code: Select all
/* Mandelbr.c                                    */
/*                                               */
/* Generates ASCII output of the Mandelbrot set  */
/* using (long) 32-bit integer math.             */
/*                                               */
/* Written by Wayne Parham                       */


#include "stdio.h"


#define DISP_COLS      80
#define DISP_ROWS      23
#define INIT_LEFT     -560  /* (x size 800, 80 steps) */
#define INIT_RIGHT     240
#define INIT_TOP       220  /* (y size 460, 23 steps) */
#define INIT_BOTTOM   -240
#define INIT_X_INCR     10  /* (step ratio 2:1, y:x ) */
#define INIT_Y_INCR     20
#define INIT_MAX_ITER   20

long random;                /* pseudo-random number */


void mandelbrot( boundL, boundR, boundT, boundB, x_incr, y_incr, max, disbuf, disp )
   long boundL, boundR, boundT, boundB, x_incr, y_incr, max;
   char* disbuf;
   char  disp;
{

   long n,cx,cy,zx,zy,zxx,zyy;
   char p;
   int  i;

   i = 0;

   for( cy=boundT; cy > boundB; cy -= y_incr ) {
      for( cx=boundL; cx < boundR; cx += x_incr ) {
         n  = 0;
         zy = 0;
         zx = 0;
         p  = ' ';
    while( n < max ) {
            zxx = zx * zx / 200;
            zyy = zy * zy / 200;
            if( (zxx + zyy) > 800 ) {
               if( n > 9 )
                  p = '0';
               else
                  p = n + 48;
               n = max;
            }
            zy = zx * zy / 100 + cy;
            zx = zxx - zyy + cx;
            n++;
         }
         disbuf[i] = p;
    i++;
         if( disp )
            putchar( p );
      }
      if( disp )
         putchar( '\n' );
   }
}


void display( disbuf )  char* disbuf;  {
   int i,x,y;
   i = 0;

   for( y=0; y < DISP_ROWS; y++ ) {
      putchar( '\n' );
      for( x=0; x < DISP_COLS; x++ ) {
         putchar( disbuf[i] );
         i++;
      }
   }
   putchar( '\n' );
}


char getcmd() {
   char cmd;
   cmd = getchar();
   switch( cmd ) {
      case 'A' :
      case 'a' :
         puts( "Entering auto mode." );
         break;
      case 'L' :
      case 'l' :
         puts( "Moving left and re-rendering." );
         break;
      case 'R' :
      case 'r' :
         puts( "Moving right and re-rendering." );
         break;
      case 'U' :
      case 'u' :
         puts( "Moving up and re-rendering." );
         break;
      case 'D' :
      case 'd' :
         puts( "Moving down and re-rendering." );
         break;
      case '.' :
         puts( "Re-centering and re-rendering." );
         break;
      case '+' :
         puts( "Zooming in and re-rendering." );
         break;
      case '-' :
         puts( "Zooming out and re-rendering." );
         break;
      case 'X' :
      case 'x' :
         puts( "Enjoy your day!" );
         break;
      default  :
         break;
   }
   return( cmd );
}


void autorender( boundL, boundR, boundT, boundB, x_incr, y_incr, max, disbuf )
   long boundL, boundR, boundT, boundB, x_incr, y_incr, max;
   char* disbuf;
{

   int i, move;
   long plot_points, setnum, setratio;
   plot_points = DISP_COLS * DISP_ROWS;

   while( 1 ) {
      setnum = 0;
      for( i=0; i < plot_points; i++ ) {
         if( disbuf[i] == ' ' ) {
            setnum++;
         }
      }
      setratio = 100 * setnum / plot_points;
      if( (setratio > 90) || (setratio < 10) ) {
         boundL = INIT_LEFT;
         boundR = INIT_RIGHT;
         boundT = INIT_TOP;
         boundB = INIT_BOTTOM;
         x_incr = INIT_X_INCR;
         y_incr = INIT_Y_INCR;
         max    = INIT_MAX_ITER;
      } else {
         move = rand() % 6;
         switch( move ) {
            case 0 :
               if( x_incr > 1 ) {
                  if( x_incr != 5 ) {
                     x_incr = x_incr / 2;
                     y_incr = y_incr / 2;
                     boundL = boundL / 2;
                     boundR = boundR / 2;
                     boundT = boundT / 2;
                     boundB = boundB / 2;
                  } else {
                     x_incr = x_incr * 10 / 25;
                     y_incr = y_incr * 10 / 25;
                     boundL = boundL * 10 / 25;
                     boundR = boundR * 10 / 25;
                     boundT = boundT * 10 / 25;
                     boundB = boundB * 10 / 25;
                  }
               }
               break;
            case 1 :
               if( x_incr < 1280 ) {
                  if( x_incr != 2 ) {
                     x_incr = x_incr * 2;
                     y_incr = y_incr * 2;
                     boundL = boundL * 2;
                     boundR = boundR * 2;
                     boundT = boundT * 2;
                     boundB = boundB * 2;
                  } else {
                     x_incr = x_incr * 25 / 10;
                     y_incr = y_incr * 25 / 10;
                     boundL = boundL * 25 / 10;
                     boundR = boundR * 25 / 10;
                     boundT = boundT * 25 / 10;
                     boundB = boundB * 25 / 10;
                  }
               }
               break;
            case 2 :
               boundL = boundL - x_incr;
               boundR = boundR - x_incr;
               break;
            case 3 :
               boundL = boundL + x_incr;
               boundR = boundR + x_incr;
               break;
            case 4 :
               boundT = boundT + y_incr;
               boundB = boundB + y_incr;
               break;
            case 5 :
               boundT = boundT - y_incr;
               boundB = boundB - y_incr;
               break;
         }
      }

      mandelbrot( boundL, boundR, boundT, boundB, x_incr, y_incr, max, disbuf, 0 );
      display( disbuf );
   }
}


int rand() {                                                         /* Get random number */
    random = random * 1103515245 + 12345;
    return( random / 65536 ) % 100;                                  /* between 0 and 99  */
}


void srand( seed ) unsigned int seed; {
    random = seed;
}


void instructions() {
   puts( "\r" );
   puts( "The Mandelbrot set includes all two-dimensional c values for which the function" );
   puts( "z = z^2 + c does not diverge when iterated from z = 0.  It was named in tribute" );
   puts( "to the mathematician Benoit Mandelbrot, a pioneer of fractal geometry.   He was" );
   puts( "first  to see a visualization of the set on March 1, 1980, at IBM's  Thomas  J." );
   puts( "Watson Research Center in Yorktown Heights, New York." );
   puts( "\r" );
   puts( "The  Mandelbrot  set  has  its  origin  in  complex  dynamics,  a  field  first" );
   puts( "investigated by the French mathematicians Pierre Fatou and Gaston Julia at  the" );
   puts( "beginning of the 20th century. This fractal was first defined and drawn in 1978" );
   puts( "by Robert W. Brooks and Peter Matelski as part of a study of Kleinian groups." );
   puts( "\r" );
   puts( "Mandelbrot  studied the parameter space of quadratic polynomials in an  article" );
   puts( "that  appeared  in 1980.  The mathematical study of the Mandelbrot  set  really" );
   puts( "began with work by the mathematicians Adrien Douady and John H. Hubbard (1985)," );
   puts( "who  established many of its fundamental properties and named the set in  honor" );
   puts( "of Mandelbrot for his influential work in fractal geometry." );
   puts( "\r" );
   puts( "This  program  displays the set on an ASCII terminal, much  like  the  earliest" );
   puts( "computers did.  It is a throwback to the computing technologies of  yesteryear." );
   puts( "It will display the set and then allow you to zoom or scroll. You can zoom with" );
   puts( "'+'  or '-' or move in any direction with 'u', 'd', 'l' or 'r'.   Enter '.'  to" );
   puts( "reset the view.  Set auto mode with the 'a' key.   Press ENTER to continue." );
}


void main() {
   int   changed;
   int   running;
   long  hold_time;
   long  boundL = INIT_LEFT;
   long  boundR = INIT_RIGHT;
   long  boundT = INIT_TOP;
   long  boundB = INIT_BOTTOM;
   long  x_incr = INIT_X_INCR;
   long  y_incr = INIT_Y_INCR;
   long  max    = INIT_MAX_ITER;
   char* disbuf;
   char  cmd;

   disbuf = (char *) malloc (DISP_COLS * (DISP_ROWS+1));
   if( disbuf == 0x00 ) {
      puts( "Memory full.  Cannot allocate memory so program is aborting." );
   } else {
      setmem( disbuf, DISP_COLS * (DISP_ROWS+1), 0 );
      srand( &disbuf );

      instructions();
      cmd = getcmd();
     
      mandelbrot( boundL, boundR, boundT, boundB, x_incr, y_incr, max, disbuf, 1 );
     
      if( cmd == 'a' ) {
         autorender( boundL, boundR, boundT, boundB, x_incr, y_incr, max, disbuf );
      } else {
         running = 1;
         while( running ) {
            cmd = getcmd();
            changed = 1;
            switch( cmd ) {
               case 'A' :
               case 'a' :
                  autorender( boundL, boundR, boundT, boundB, x_incr, y_incr, max, disbuf, hold_time );
                  break;
               case 'X' :
               case 'x' :
                  running = 0;
                  break;
               case 'L' :
               case 'l' :
                  boundL = boundL - x_incr;
                  boundR = boundR - x_incr;
                  break;
               case 'R' :
               case 'r' :
                  boundL = boundL + x_incr;
                  boundR = boundR + x_incr;
                  break;
               case 'U' :
               case 'u' :
                  boundT = boundT + y_incr;
                  boundB = boundB + y_incr;
                  break;
               case 'D' :
               case 'd' :
                  boundT = boundT - y_incr;
                  boundB = boundB - y_incr;
                  break;
               case '.' :
                  boundL = INIT_LEFT;
                  boundR = INIT_RIGHT;
                  boundT = INIT_TOP;
                  boundB = INIT_BOTTOM;
                  x_incr = INIT_X_INCR;
                  y_incr = INIT_Y_INCR;
                  break;
               case '+' :
                  if( x_incr > 1 ) {
                     if( x_incr != 5 ) {
                        x_incr = x_incr / 2;
                        y_incr = y_incr / 2;
                        boundL = boundL / 2;
                        boundR = boundR / 2;
                        boundT = boundT / 2;
                        boundB = boundB / 2;
                     } else {
                        x_incr = x_incr * 10 / 25;
                        y_incr = y_incr * 10 / 25;
                        boundL = boundL * 10 / 25;
                        boundR = boundR * 10 / 25;
                        boundT = boundT * 10 / 25;
                        boundB = boundB * 10 / 25;
                     }
                  } else {
                     printf ( "\nMaximum zoom reached." );
                  }
                  break;
               case '-' :
                  if( x_incr < 1280 ) {
                     if( x_incr != 2 ) {
                        x_incr = x_incr * 2;
                        y_incr = y_incr * 2;
                        boundL = boundL * 2;
                        boundR = boundR * 2;
                        boundT = boundT * 2;
                        boundB = boundB * 2;
                     } else {
                        x_incr = x_incr * 25 / 10;
                        y_incr = y_incr * 25 / 10;
                        boundL = boundL * 25 / 10;
                        boundR = boundR * 25 / 10;
                        boundT = boundT * 25 / 10;
                        boundB = boundB * 25 / 10;
                     }
                  } else {
                     printf ( "\nMaximum distance reached." );
                  }
                  break;
               default  :
                  changed = 0;
                  break;
            }
            if( running && changed ) {
                mandelbrot( boundL, boundR, boundT, boundB, x_incr, y_incr, max, disbuf, 0 );
                display( disbuf );
            }
         }
      }
   }

   free( disbuf );
}


Not sure which is faster yet - BDS or Aztec - 'cause I've gotta run. I have plans for the evening. I built MANDELBR.COM on SimH and ran it, but stuff always runs fast on the simulator. So I'll be interested to put it on a real Altair 8800 and see how the Aztec C version compares to the BDS C version.

One last thing, a reflection that's not even really 2ยข worth. But it's an observation I've made, nonetheless.

Aztec C is more full-featured than BDS C, so it makes sense to create an Aztec v.1.06d diskette for your Altair if you don't already have one. Or just build programs on with it using the SimH Altair simulator.

The Aztec distribution provides a cross-assembler too, but it appears to only support the Z80. To make programs for an 8080, the native compiler/assembler/linker works fine.
Wayne Parham
 
Posts: 240
Joined: March 18th, 2022, 3:01 pm

Re: Mandelbrot Program

Postby BillO » August 10th, 2022, 8:41 pm

Great stuff Wayne!

I look forward the trying out these updates and Aztec C.

Right now I'm working on some 6502 projects and re-organizing my 'lab' so it might be a little while.
BillO
 
Posts: 136
Joined: November 11th, 2020, 6:29 am

Re: Mandelbrot Program

Postby Wayne Parham » August 11th, 2022, 8:45 am

One thing to notice - both for comparing speed and for comparing images - is the value of INIT_MAX_ITER. It definitely impacts the resolution and the speed.

You might notice the BDS version is set higher than the Aztec version because I was playing around with the value. Set it to whatever you like but comparisons between compilers should probably be done with that value set the same.
Wayne Parham
 
Posts: 240
Joined: March 18th, 2022, 3:01 pm

Re: Mandelbrot Program

Postby GlassTTY » September 18th, 2022, 4:31 am

Many thanks for this, I have built it with Aztec C and it works a treat.

J
GlassTTY
 
Posts: 6
Joined: November 14th, 2019, 8:34 pm


Return to General Discussions

Who is online

Users browsing this forum: Google [Bot] and 18 guests

cron