Look
Toward
the
Past

by Thomas Leylan

Published in Greg Lief's Aquarium, Nov. 1995 Issue

If I had to describe the secret to good Clipper programming in a single phrase I would have to say "look toward the past with an eye toward the future."

Why would I suggest looking backwards? Because programming in Clipper is not significantly different from programming in C, Pascal or BASIC nor would I imagine particularly different from programming in Ada, COBOL, Forth, FORTRAN or SmallTalk.

Oh sure the syntax is different and the structure is different and memory management is different and about a million other things are different but other than that, they're identical.

And to me anyway, that's great! Why, because it means that the resource pool of information relevant to the Clipper developer is far larger than we are ordinarily led to believe.

Information is all around us...

To tell you the truth, it's a rare moment when I pick up a Clipper book for information on programming. Sure I check the manual when I forget the order of function arguments or pop up a Norton Guide file to check the return value of DBSKIP() (it returns NIL by the way though for the life of me I can't see why) but the sections on recursion, linked-lists, binary trees and such is... well "non-existent" pretty well describes it.

And no, I'm not suggesting that you should forget the obvious sources of Clipper programming ideas but rather that you should not overlook the less than obvious sources.

Personally I find books on C and C++ extremely useful as well as non-language specific books on the fundamentals of application design and over the years I've read books on OOPs, Actor, Turbo Assembler, MSVC++, Windows, X-Windows, Ada, writing compilers and the evolution of computer languages.

To list just a few:

   Object-oriented Software Construction
   by Bertrand Meyer
   1988 Prentice-Hall International (ISBN 0-13-629049-3)

   Object-Oriented Programming Featuring Actor
   by Marty Franz
   1990 Scott, Foresman and Company (ISBN 0-673-38641-4)

   Compiler Design in C
   by Allen I. Holub
   1990 Prentice-Hall, Inc. (ISBN 0-13-155045-4)

   Reusability & Software Construction
   by Jerry D. Smith
   John Wiley + Sons (ISBN 0-471-52411-5)

   Programming, The Impossible Challenge
   by Bob Walraet
   North-Holland (ISBN 0-444-87128-4)

   Object-Oriented Software
   by Ann L. Winblad
   Addison-Wesley (ISBN 0-201-50736-6)

   Managing the System Life Cycle
   by Edward Yourdon
   Yourdon Press (ISBN 0-917072-26-X)

   Analysis, Design & Implementation of Data Dictionaries
   by Ken S. Brathwaite
   1988 McGraw-Hill (ISBN 0-07-007248-5)

   The Practitioner's Blueprint for Logical and Physical Database Design
   by Eric Garrigue Vesely
   1986 Prentice-Hall (ISBN 0-13-694267-9)

Two standard references, never far from my desk, are The C Programming Language also known as the "white book", because of its white cover and The Elements of Programming Style.

   The C Programming Language
   by Brian W. Kernighan and Dennis Ritchie
   1978, 1988 Prentice-Hall, Inc. (ISBN 0-13-1103262-8)

   The Elements of Programming Style
   by Kernighan and Plauger
   1974 McGraw-Hill Book Company (ISBN 07-034199-0)

But, to get to the point. I'm simply saying that the equation we use to center a message on the screen, that leap year algorithm, that 24 to 12 hour time conversion function, that random number generator and that shuffle routine along with so many other things that we labor over were already written long ago.

Nothing new under the sun...

Pythagoras didn't program in Clipper and neither does Brian Kernighan or Niklaus Wirth (as far as I know) but their accomplishments are no less useful for it. Luckily they did use C and Pascal (except for Pythagoras) and armed with the source code (or the formula) we can usually translate to Clipper fairly easily.

Before you scream "but I don't know Pascal" let me point out that you don't actually need to know Pascal or C to figure out what's going on in a short routine. Though I admit familiarity would help, one can often pick up the algorithmic "signal" while filtering the language-specific "noise".

Good information is good information regardless of the language used. Read for instance just a few of the many one-liners from The Elements of Programming Style, a book first published in 1974.

Though written 20 years ago these examples apply to our Clipper work today and the admonition against comparing floating point values is of particular interest. Hardly a month goes by where someone doesn't rediscover this fact and reports "floating point bugs" on the Clipper Forum (CA's forum on CompuServe) or on the Internet newsgroup, comp.lang.clipper.

A simple example using C...

In the book, The C Programming Language a very simple example is used to illustrate C syntax and program structure. The code, reprinted here, outputs a table of Fahrenheit temperatures with their centigrade equivalents.

   /* fahr.c */


   #include <stdio.h>

   /* print Fahrenheit-Celsius table
      for fahr = 0, 20, ..., 300 */

   main()
   {
      int fahr, celsius;
      int lower, upper, step;

      lower = 0;     /* lower limit of temperature table */
      upper = 300;   /* upper limit */
      step = 20;     /* step size */

      fahr = lower;
      while (fahr <= upper) {
         celsius = 5 * (fahr - 32) / 9;
         printf("%d\t%d\n", fahr, celsius);
         fahr = fahr + step;
      }
   }

I don't know what C code looks like from the "never having seen C code perspective" but I hope it looks familiar enough so that you recognize the following Clipper translation.

   /* fahr.prg */

   /* print Fahrenheit-Celsius table
      for fahr = 0, 20, ..., 300 */

   FUNCTION main()

      local fahr, celsius
      local lower, upper, step

      lower = 0      /* lower limit of temperature table */
      upper = 300    /* upper limit */
      step = 20      /* step size */

      fahr = lower
      while (fahr <= upper)
         celsius = 5 * (fahr - 32) / 9
         qout(fahr, celsius)
         fahr = fahr + step
      end

      return 0

I purposely kept the code close to C but this was not my main point, rather I wanted to illustrate that with minor adjustments (in this case a few semicolons, some braces and a handful of variable declarations), the resources of the last 30 years are available today.

An interesting example using Pascal...

Lest you think that I'm suggesting you spend the next few months pouring over C books, I present the following example from Mastering Turbo Pascal.

   Mastering Turbo Pascal 4.0, Second Edition
   by Tom Swan
   1988 Hayden Books (ISBN 0-672-48421-8)

The following code performs a binary sort and is given as an example of how to write in Turbo Pascal.

   { sortdemo.pas }

   PROGRAM BinarySort;
   CONST
      MaxElements = 100; { Maximum array size }
   TYPE
      Element = Integer;
      ElementArray = ARRAY[ 1 .. MaxElements ] of Element;
   VAR
      a : ElementArray;
      i, n : Integer;

   PROCEDURE Sort( VAR a : ElementArray; n : Integer );
   { n = actual number elements in array a }
   { Algorithm = Binary Insertion }

   VAR
      i, j, Bottom, Top, Middle : Integer;
      Temp : Element;

   BEGIN
      FOR i := 2 to n DO
      BEGIN
         Temp := a[ i ]; Bottom := 1; Top := i - 1;

         WHILE Bottom <= Top DO
         BEGIN
            Middle := ( Bottom + Top ) DIV 2;
            IF Temp < a[ Middle ]
               THEN Top := Middle - 1
               ELSE Bottom := Middle + 1
            END; { while }

            FOR j := i - 1 DOWNTO Bottom DO
               a[ j + 1 ] := a[ j ]

            a[ Bottom ] := Temp

         END { for }
      END; { Sort }
   BEGIN

      Writeln( 'Binary Insertion Sort' );
      Writeln;

      REPEAT
         Write( 'How many ? (2 to ', MaxElements, ') ? ' );
         Readln( n )
      UNTIL n <= MaxElements;

      FOR i := 1 to n DO
      BEGIN
         a[ i ] := Random( Maxint );
         Write( a[ i ]:8 )
      END; { for }

      Writeln; Writeln;

      Sort( a, n );

      FOR i := 1 to n DO
         Write( a[ i ]:8 );

      Writeln;
   END.

I think Pascal looks "English" enough that a translation to Clipper shouldn't require reading more than a few pages from a Pascal manual if we get stuck along the way. We can guess that CONST means "constants" and that VAR means "variables", writeln() is an output function, etc.

   // sortdemo.prg
   // PROGRAM BinarySort
   // CONST
      #define MaxElements 100 // Maximum array size
   // TYPE
      memvar getlist

   // VAR
      STATIC a
      STATIC i, n := 0

   PROCEDURE Begin
      a := ARRAY( MaxElements )
      Main()

   PROCEDURE Sort( a, n );
   // n = actual number elements in array a
   // Algorithm = Binary Insertion

   // VAR
      LOCAL i, j, Bottom, Top, Middle
      LOCAL Temp

   // BEGIN

      FOR i := 2 to n
         Temp := a[ i ]; Bottom := 1; Top := i - 1

         WHILE Bottom <= Top
            Middle := Int(( Bottom + Top ) / 2)
            IF Temp < a[ Middle ]
               Top := Middle - 1
            ELSE
               Bottom := Middle + 1
            ENDIF
         END

         FOR j := i - 1 TO Bottom STEP -1
            a[ j + 1 ] := a[ j ]
         NEXT j

         a[ Bottom ] := Temp

      NEXT i

   // END { Sort }


   PROCEDURE Main

      QOut( 'Binary Insertion Sort' )
      QOut()

      WHILE (.T.)
         @ Row(), Col() Say ;
            'How many ? (2 to ' + Str(MaxElements, 3, 0) + ') ? '
         @ Row(), Col() Get n
         Read
         QOut()
         IF (n < MaxElements); EXIT; ENDIF
      END

      FOR i := 1 to n
         a[ i ] := Random( Seconds() + i )
         Qout( a[ i ] )
      NEXT i

      QOut(); QOut()

      Sort( a, n )

      FOR i := 1 to n
         Qout( a[ i ] )
      NEXT i

      QOut()

      // END.
   FUNCTION Random( nSeed )
      RETURN INT((((( nSeed * 31415821 ) + 1 );
               % 1000000 ) / 1000000 ) * 32767 )

This time I tried to keep it reasonably close to Pascal and if I thought it was important I could make it look even more like Pascal through the use of Clipper's preprocessor directives. Again however. I'm not trying to turn you into a Pascal programmer but rather pointing out that binary sorting algorithms can honestly be termed "ancient" and that one cannot "invent" a binary sort but can only resurrect it.

It works in Pascal, it works in Clipper, what a shock!

A terrific example using Z-80 assembler...

I really wanted to reach into the past for an example and I hope that Zilog appreciates my mention of their classic CPU. Proving that one just doesn't know where the next algorithm will turn up.

This one is from a book called Z80 Assembly Language Programming.

   Z80 Assembly Language Programming
   by Lance A. Leventhal
   1979 Osborne/McGraw-Hill (ISBN 0-931988-21-7)
   ; led.asm

            LD       A,00001111B ;MAKE PORT B OUTPUT
            OUT      (PIOCRB),A
            LD       B,BLANK     ;GET BLANK CODE
            LD       A,(40h)     ;GET DATA
            CP       10          ;IS DATA A DECIMAL DIGIT?
            JR       NC,DSPLY    ;NO, DISPLAY BLANKS
            LD       DE,SSEG     ;GET BASE ADDRESS OF 7-SEGMENT TABLE
            LD       H,0         ;MAKE DATA INTO A 16-BIT INDEX
            LD       L,A
            ADD      HL,DE       ;ACCESS ELEMENT IN TABLE
            LD       B,(HL)      ;GET 7-SEGMENT CODE
   DSPLAY:  LD       A,B
            OUT      (PIODRB),A
            HALT
            ORG      20H
   SSEG:    DEFB     3FH
            DEFB     06H
            DEFB     5BH
            DEFB     4FH
            DEFB     66H
            DEFB     6DH
            DEFB     7DH
            DEFB     07H
            DEFB     7FH
            DEFB     6FH
   BLANK:   DEFB     00H

OK, I'm willing to give in a little here and admit that you might need a little assembler experience to read this code but you will probably be surprised how much you can get out of it just by reading slowly.

   LD A,00001111B
   Load (LD) the accumulator (A) with 15 binary (00001111B)

   CP 10
   Compare (CP) the accumulator (it's implied) with 10 decimal (10)

   OUT (PIODRB),A
   Output (OUT) to address (PIODRB), the accumulator (A)

   DEFB 06H
   Define Byte (DEFB) at this location the value 6 hex (06H)

The translation also requires some "wrapper" code since the Z-80 sample provided is the heart of the operation and was not intended to run "as is".

   /* led.prg */

   #define LED_H REPL( CHR(196), 5 )
   #define LED_V CHR(179)

   #define BLANK 11

   STATIC aSeg := { 63, 6, 91, 79, 102, 109, 125, 7, 127, 103, 0 }


   PROCEDURE Led
      LOCAL xCursor := SET( _SET_CURSOR, 0 )

      LOCAL nKey, cKey
      LOCAL aLed := LedNew( 10, 10, { "+W", "+N" } )

      LOCAL nCode := aSeg[ BLANK ]

      SCROLL()

      PIODRB( aLed, nCode )                  // DISPLAY BLANK

      @ 1, 0 SAY "Press keys 0 - 9 or ESC to exit"

      WHILE (( nKey := INKEY( 0 )) != 27 )   // GET DATA

         IF IsDigit( cKey := CHR( nKey ))    // IS DATA A DECIMAL DIGIT?
            nCode := aSeg[ VAL( cKey ) + 1 ] // GET 7-SEGMENT CODE
            PIODRB( aLed, nCode ) // DISPLAY
         ENDIF

      END

      SET( _SET_CURSOR, xCursor)
      RETURN


   FUNCTION LedNew( nRow, nCol, aColor )
      RETURN {{ nRow    , nCol + 1, LED_H },;
              { nRow + 1, nCol + 6, LED_V },;
              { nRow + 3, nCol + 6, LED_V },;
              { nRow + 4, nCol + 1, LED_H },;
              { nRow + 3, nCol    , LED_V },;
              { nRow + 1, nCol    , LED_V },;
              { nRow + 2, nCol + 1, LED_H },;
              aColor }


   PROCEDURE PIODRB( aLed, nCode )
      LOCAL nSeg

      FOR nSeg := 1 TO 7
         @ aLed[ nSeg, 1 ], aLed[ nSeg, 2 ] ;
            SAY aLed[ nSeg, 3 ] ;
            COLOR aLed[ 8, BitSet( nCode, nSeg - 1 ) ]
      NEXT

      RETURN


   FUNCTION BitSet( nByte, nBit )
      RETURN IF( INT((( nByte * ;
         ( 2 ^ ( 7 - nBit ))) % 256 ) / 128 ) == 1, 1, 2 )

Perhaps an explanation is in order.

The original Z80 code is addressing a parallel input/output circuit (PIO) in the computer to which would be connected a seven-segment LED. The coding scheme used to represent the segments is fixed by the hardware and requires a call to the control register (PIOCR) and the data register (PIODR). There are two of them so they end up named things like PIOCRA and PIODRB.

If this wasn't a great explanation (and I'm sure it wasn't) you'll have to talk to Mr. Leventhal. I got lost somewhere around the words "parallel input/output circuit".

In short, the segments look like this:

      .-- a --.

      f       b

      .-- g --.

      e       c

      .-- d --.

Don't judge a book by its subject matter...

Z80 Assembly Language Programming is one example of a book that is considerably more than what the title suggests. It is packed full of stuff.

Chapter 1, "Introduction to Assembly Language Programming" and the chapter on "Assemblers" along with a reference guide to the Z80 instruction set would be expected in such a book. The sample programs are a bonus and the final few chapters are a well thought out treatise on, "The Tasks of Software Development".

Mr. Leventhal, discusses the stages of software development

Do these things sound familiar? It seems that designing applications in Z80 assembler in 1979 is similar to designing applications in Clipper in 1995.

Among the things this book taught me was how an LED operates. If I hadn't seen the Z-80 example I surely wouldn't have written a simulator quite this way. The trick is encoding the LED digits into single bytes by working at the bit level. This lets us represent the entire set in just 10 bytes.

A similar solution could have been managed using 7 bytes per digit (70 bytes total) or even 5 strings per digit for a total of 350 bytes for the set but clearly neither of those solutions would be optimal.

Wrapping up the past and entering the present...

I've encountered dozens of routines that could prove useful to the Clipper developer. In some cases (when written in C or ASM,) the code can be conformed to the rules imposed by Clipper's extend system and simply linked in. In other cases (as I hope you you've seen,) they can be translated into Clipper.

Check out books by Niklaus Wirth and Donald E. Knuth. Books that present code on UNIX tools and general computer science books on stacks, queues, linked-lists and b-trees are filled with great information. Do you work on a network? Many of the Novell network services are directly available to Clipper developers if people would only be willing to take a slightly indirect route to get them.

And one for fun...

Some months back as I was preparing for my sessions at the Netherland's Clipper Devcon I was browsing newsgroups on the Internet. I happened to be skimming some articles in one of the graphic-related newsgroups and stumbled upon a message from a gentleman in Montreal who wrote:

   "I'm trying to do a 'bee swarm' effect with no luck. If someone
   has already made it or has some pseudo code to do it, I'd
   appreciate a copy or any help on doing this effect. (Preferably
   using Borland C 3.1 under DOS using mode 13h, but ANY help would
   be GREATLY appreciated!)"

A reply from Frank Compagner, (fc@butler.fee.uva.nl) at the University of Amsterdam was followed by Pascal code (with imbedded assembler) he had written to demonstrate a swarm effect. That original code is reproduced here as a point of reference.

   {written by Frank Compagner, (fc@butler.fee.uva.nl) }

   USES Dos,Crt;

   CONST
      VGA = $A000;
      n = 100;
      maxq = 4;
      maxb = 2;

   TYPE
      beetype = RECORD
         x,y : real;
         sx,sy : real;
   END;

   VAR
      bee1,bee0 : ARRAY[0..n] OF beetype;


   FUNCTION GetKey : byte;

      VAR
         k:byte;

      BEGIN
         ASM
            mov ah,6
            mov dl,$FF
            int $21
            mov k,al
         END;
         GetKey:=k;
      END;


   PROCEDURE WaitRetrace; ASSEMBLER;


      LABEL
         l1,l2;

      ASM
          mov dx,3DAh
      l1: in  al,dx
          and al,08h
          jnz l1
      l2: in  al,dx
          and al,08h
          jz  l2
      END;


   PROCEDURE Setup;

   VAR
      i : integer;

   BEGIN
      ASM
         mov ax,0013h
         int 10h
      END;
      Randomize;
      FOR i:=0 TO n DO
         WITH bee0[i] DO
         BEGIN
            x:=280*Random+20;
            y:=160*Random+20;
            sx:=2*Random-1;
            sy:=2*Random-1;
         END;
      END;


   PROCEDURE Move;

   VAR
      i : integer;
      dx,dy,d : real;

   BEGIN
      FOR i:=0 TO n DO
         bee1[i]:=bee0[i];
         WITH bee0[0] DO
         BEGIN
            sx:=sx+Random-0.5;
            IF sx>maxq THEN
               sx:=maxq;
            IF sx<-maxq THEN
               sx:=-maxq;
            sy:=sy+Random-0.5;
            IF sy>maxq THEN
               sy:=maxq;
            IF sy<-maxq THEN
               sy:=-maxq;
            x:=x+sx;
            y:=y+sy;
            IF (x<20) OR (x>299) THEN
               BEGIN
                  sx:=-sx;
                  x:=x+2*sx;
               END;
            IF (y<20) OR (y>179) THEN
               BEGIN
                  sy:=-sy;
                  y:=y+2*sy;
               END;
         END;
         FOR i:=1 TO n DO
            WITH bee0[i] DO
            BEGIN
               dx:=bee0[0].x-x;
               dy:=bee0[0].y-y;
               d:=Sqr(dx)+Sqr(dy);
               sx:=sx+8*dx/d+Random-0.5;
               sy:=sy+8*dy/d+Random-0.5;
               IF sx>maxb THEN
                  sx:=maxb;
               IF sx<-maxb THEN
                  sx:=-maxb;
               IF sy>maxb THEN
                  sy:=maxb;
               IF sy<-maxb THEN
                  sy:=-maxb;
               x:=x+sx;
               y:=y+sy;
               IF (x<0) OR (x>319) THEN
               BEGIN
                  sx:=-sx;
                  x:=x+2*sx;
               END;
               IF (y<0) OR (y>199) THEN
               BEGIN
                  sy:=-sy;
                  y:=y+2*sy;
               END;
            END;
      END;


   PROCEDURE Display;

   VAR
      i : integer;

   BEGIN
      WaitRetrace;
      FOR i:=0 TO n DO
         WITH bee1[i] DO
            Mem[vga:320*((200+Round(y)) MOD 200)+(320+Round(x)) MOD 320]:=0;
      FOR i:=1 TO n DO
         WITH bee0[i] DO
            Mem[vga:320*((200+Round(y)) MOD 200)+(320+Round(x)) MOD 320]:=10;
         WITH bee0[0] DO
            Mem[vga:320*((200+Round(y)) MOD 200)+(320+Round(x)) MOD 320]:=12;
   END;

   BEGIN
      SetUp;
      REPEAT
         Move;
         Display;
      UNTIL GetKey=27;
      ASM
         mov ax,0003h
         int 10h
      END;
   END.

Since I had been telling people how easily converting routines from other languages to Clipper is, I thought I'd take up the challenge with this one.

Targeting it for Clipper 5.2 I knew I would need a routine to set the video graphics mode and to plot a pixel and the easiest way to accomplish that is by using a function found in the Nanforum library. FT_INT86() was written by Ted Means and permits us to directly call DOS interrupts. I've used it to call the DOS video interrupt. The entire library is available free of charge from many places including CompuServe and the Internet but I've included the components needed to link BEES, on the disk.

I began by creating as direct a translation as I could and after the bees were indeed swarming I took on the task of optimizing the little buzzers which leaves us with the following:

  /* bees.prg - by Tom Leylan

     compile: CLIPPER bees /m/n/w/l
        link: BLINKER FI bees, cint86, aint86
   */

   #include "ftint86.ch"
   #include "inkey.ch"

   #define SETPIXEL 12
   #define GETMODE 15
   #define VID_INT 16
   #define GR_MODE 13

   #define BEE_0 10
   #define BEE_1 12

   #define MAXN 30 	// number of bees
   #define MAXQ 4
   #define MAXB 2

   #define PX 1
   #define PY 2
   #define SX 3
   #define SY 4

   #define SQR(x) ((x)*(x))

   STATIC nRnd

   #translate Rnd() => ;
      (( nRnd := (( nRnd * 31415621 + 1 ) % 100000000 )) / 100000000 )

   STATIC aBees[ MAXN ]


   FUNCTION Main()
      LOCAL xMode := SetMode( GR_MODE )

      nRnd := SECONDS()

      AEVAL( aBees,;
         {| uVal, nEle | aBees[ nEle ] :=;
         { (( 280 * Rnd() ) + 20 ), (( 160 * Rnd() ) + 20 ),;
           (( 2 * Rnd() ) - 1 ), (( 2 * Rnd() ) - 1 ) }} )

      Plot()

      SetMode( xMode )

      RETURN 0


   PROCEDURE Plot
      STATIC aRegs[ INT86_MAX_REGS ]

      LOCAL i, dx, dy, d

      aRegs[ BX ] := 0						// BH = page number

      WHILE !( INKEY() == K_ESC )

         aRegs[ CX ] := aBees[ 1, PX ]		// CX = column
         aRegs[ DX ] := aBees[ 1, PY ]		// DX = row

         aBees[ 1, SX ] += (( Rnd() - 0.5 ))

         aBees[ 1, SX ] :=;
            MAX(( aBees[ 1, SX ] := MIN( aBees[ 1, SX ], MAXQ )), -MAXQ )

         aBees[ 1, PX ] += aBees[ 1, SX ]
         aBees[ 1, SY ] += (( Rnd() - 0.5 ))

         aBees[ 1, SY ] :=;
            MAX(( aBees[ 1, SY ] := MIN( aBees[ 1, SY ], MAXQ )), -MAXQ )

         aBees[ 1, PY ] += aBees[ 1, SY ]

         IF (( aBees[ 1, PX ] < 20 ) .OR. ( aBees[ 1, PX ] > 299 ))
            aBees[ 1, SX ] *= -1
            aBees[ 1, PX ] += ( 2 * aBees[ 1, SX ] )
         ENDIF

         IF (( aBees[ 1, PY ] < 20 ) .OR. ( aBees[ 1, PY ] > 179 ))
            aBees[ 1, SY ] *= -1
            aBees[ 1, PY ] += ( 2 * aBees[ 1, SY ] )
         ENDIF

         aRegs[ AX ] := ( MAKEHI( SETPIXEL ))
         FT_INT86( VID_INT, aRegs )

         aRegs[ AX ] := ( MAKEHI( SETPIXEL ) + BEE_1 )
         aRegs[ CX ] := aBees[ 1, PX ]		// CX = column
         aRegs[ DX ] := aBees[ 1, PY ]		// DX = row
         FT_INT86( VID_INT, aRegs )

         FOR i := 2 TO MAXN

            aRegs[ CX ] := aBees[ i, PX ]	// CX = column
            aRegs[ DX ] := aBees[ i, PY ]	// DX = row

            dx := aBees[ 1, PX ] - aBees[ i, PX ]
            dy := aBees[ 1, PY ] - aBees[ i, PY ]

            d := SQR( dx ) + SQR( dy )

            aBees[ i, SX ] += ( ((( 8 * dx ) / d ) + Rnd() ) - 0.5 )

            aBees[ i, SX ] :=;
               MAX(( aBees[ i, SX ] := MIN( aBees[ i, SX ], MAXB )), -MAXB )

            aBees[ i, PX ] += aBees[ i, SX ]

            aBees[ i, SY ] += ( ((( 8 * dy ) / d ) + Rnd() ) - 0.5 )

            aBees[ i, SY ] :=;
               MAX(( aBees[ i, SY ] := MIN( aBees[ i, SY ], MAXB )), -MAXB )

            aBees[ i, PY ] += aBees[ i, SY ]

            IF (( aBees[ i, PX ] < 0 ) .OR. ( aBees[ i, PX ] > 319 ))
               aBees[ i, SX ] *= -1
               aBees[ i, PX ] += ( 2 * aBees[ i, SX ] )
            ENDIF

            IF (( aBees[ i, PY ] < 0 ) .OR. ( aBees[ i, PY ] > 199 ))
               aBees[ i, SY ] *= -1
               aBees[ i, PY ] += ( 2 * aBees[ i, SY ] )
            ENDIF

            aRegs[ AX ] := ( MAKEHI( SETPIXEL ))
            FT_INT86( VID_INT, aRegs )
            aRegs[ AX ] := ( MAKEHI( SETPIXEL ) + BEE_0 )
            aRegs[ CX ] := aBees[ i, PX ]	// CX = column
            aRegs[ DX ] := aBees[ i, PY ]	// DX = row
            FT_INT86( VID_INT, aRegs )

         NEXT

      END

      RETURN


   FUNCTION SetMode( nMode )
      LOCAL aRegs[ INT86_MAX_REGS ]
      LOCAL nRet

      aRegs[ AX ] := MAKEHI( GETMODE )
      FT_INT86( VID_INT, aRegs )

      nRet := LOWBYTE( aRegs[ AX ] )

      aRegs[ AX ] := nMode
      FT_INT86( VID_INT, aRegs )

      RETURN nRet

The compiled Pascal example is included on the disk and when you run it you will notice a considerable difference in execution speed. Native code compiling strikes again. I've also limited the bee count to 30 rather than use the 100 bees defined in the Pascal source. All in all however, I'm just proving that it works and not suggesting you write real-time, graphical simulations in Clipper.

Keep an eye toward the future...

We can learn a lot from the past but we have to think about and also plan for the future and the future in this case is OOPS. Object-oriented programming is here to stay as is MS Windows or generically speaking, graphical, windowing, operating systems.

The time when DOS-based, text applications could "wow" the audience has passed. No doubt these apps still work and will work for the next couple of years but the advantages to the client, the designer and the developer of multi-tasking, drag and drop, cut and paste and device independence are too great to be ignored.

My advice is don't wait. Don't wait for Computer Associates or any other product vendor to lead the parade. Grab books on related subjects and start studying them now. Then, when the time to choose new tools arrives, you can make an informed decision. Hopefully, now that you've seen how interchangeable programming concepts are, a decision based, not upon some similarity to Clipper but rather the actual features of the product or language.

Remember we're programmers first. We are not C, Pascal, BASIC or in this case Clipper programmers, we are programmers who use C, Pascal, BASIC or in this case, Clipper.

About the author:

Tom Leylan has been a guest speaker at conferences around the world including events in Amsterdam, London, Sydney, Johannesburg, Miami Beach, Orlando, Los Angeles, Palm Desert and Honolulu. He has written numerous articles and is the author of the book, Writing Applications with Clipper © 1994 MIS Press ISBN: 1-55851-382-5.

Tom can be reached at: postmaster@leylan.com

© 1995, The Leylan Factor (All Rights Reserved)