Mandelbrot Program
Posted: 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:
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;
}