OSDN Git Service

compy4 sync
authorsparky4 <sparky4@cock.li>
Thu, 22 Jan 2015 18:56:12 +0000 (12:56 -0600)
committersparky4 <sparky4@cock.li>
Thu, 22 Jan 2015 18:56:12 +0000 (12:56 -0600)
deleted:    16/Q.BAT
new file:   16/fcsp2src.zip
deleted:    16/lib/TYPES.H
deleted:    16/lib/types.h
deleted:    16/q.bat
new file:   16/roads/ANIM.C
new file:   16/roads/BOOKENDS.C
new file:   16/roads/FX.C
new file:   16/roads/FX.H
new file:   16/roads/INITROAD.C
new file:   16/roads/INITW.C
new file:   16/roads/KEYS.H
new file:   16/roads/MAKEFILE.MAK
new file:   16/roads/READ.ME
new file:   16/roads/ROADS.C
new file:   16/roads/ROADS.EXE
new file:   16/roads/ROADS.GIF
new file:   16/roads/ROADS.H
new file:   16/roads/TILEIO.C
new file:   16/roads/TILES.H
new file:   16/roads/VERSION.H
new file:   16/starport2/FCINFO12.TXT
new file:   16/starport2/FILE_ID.DIZ
new file:   16/starport2/MAKE.BAT
new file:   16/starport2/README
new file:   16/starport2/SP2.ASM
new file:   16/starport2/SP2.COM
modified:   Project 16.bfproject
deleted:    SCROLL.SMP
new file:   doc/CGUIDE_3.TXT
modified:   makefile
modified:   pcxtest.exe
new file:   planarNotes.txt
modified:   scroll.exe
new file:   src/lib/PLANAR.C
new file:   src/lib/ems.c
new file:   src/lib/ems1.c
modified:   src/lib/lib_head.h
modified:   src/lib/modex16.c
modified:   src/lib/modex16.h
new file:   src/lib/planar.h
new file:   src/lib/xmem/xmem.asm
new file:   src/lib/xmem/xmem.h
new file:   src/lib/xmem/xmem.txt
new file:   src/lib/xmem/xmemc.c
new file:   src/lib/xms.c
new file:   src/lib/xms.h
modified:   src/scroll.c
modified:   src/test2.c
modified:   test.exe
modified:   test2.exe

51 files changed:
16/Q.BAT [deleted file]
16/fcsp2src.zip [new file with mode: 0644]
16/lib/TYPES.H [deleted file]
16/lib/types.h [deleted file]
16/q.bat [deleted file]
16/roads/ANIM.C [new file with mode: 0644]
16/roads/BOOKENDS.C [new file with mode: 0644]
16/roads/FX.C [new file with mode: 0644]
16/roads/FX.H [new file with mode: 0644]
16/roads/INITROAD.C [new file with mode: 0644]
16/roads/INITW.C [new file with mode: 0644]
16/roads/KEYS.H [new file with mode: 0644]
16/roads/MAKEFILE.MAK [new file with mode: 0644]
16/roads/READ.ME [new file with mode: 0644]
16/roads/ROADS.C [new file with mode: 0644]
16/roads/ROADS.EXE [new file with mode: 0644]
16/roads/ROADS.GIF [new file with mode: 0644]
16/roads/ROADS.H [new file with mode: 0644]
16/roads/TILEIO.C [new file with mode: 0644]
16/roads/TILES.H [new file with mode: 0644]
16/roads/VERSION.H [new file with mode: 0644]
16/starport2/FCINFO12.TXT [new file with mode: 0644]
16/starport2/FILE_ID.DIZ [new file with mode: 0644]
16/starport2/MAKE.BAT [new file with mode: 0644]
16/starport2/README [new file with mode: 0644]
16/starport2/SP2.ASM [new file with mode: 0644]
16/starport2/SP2.COM [new file with mode: 0644]
Project 16.bfproject
SCROLL.SMP [deleted file]
doc/CGUIDE_3.TXT [new file with mode: 0644]
makefile
pcxtest.exe
planarNotes.txt [new file with mode: 0644]
scroll.exe
src/lib/PLANAR.C [new file with mode: 0644]
src/lib/ems.c [new file with mode: 0644]
src/lib/ems1.c [new file with mode: 0644]
src/lib/lib_head.h
src/lib/modex16.c
src/lib/modex16.h
src/lib/planar.h [new file with mode: 0644]
src/lib/xmem/xmem.asm [new file with mode: 0644]
src/lib/xmem/xmem.h [new file with mode: 0644]
src/lib/xmem/xmem.txt [new file with mode: 0644]
src/lib/xmem/xmemc.c [new file with mode: 0644]
src/lib/xms.c [new file with mode: 0644]
src/lib/xms.h [new file with mode: 0644]
src/scroll.c
src/test2.c
test.exe
test2.exe

diff --git a/16/Q.BAT b/16/Q.BAT
deleted file mode 100644 (file)
index 5d1b584..0000000
--- a/16/Q.BAT
+++ /dev/null
@@ -1,5 +0,0 @@
-call hres\r
-vi dos_gfx.cpp\r
-call x\r
-pause\r
-dos_gfx.exe\r
diff --git a/16/fcsp2src.zip b/16/fcsp2src.zip
new file mode 100644 (file)
index 0000000..4885231
Binary files /dev/null and b/16/fcsp2src.zip differ
diff --git a/16/lib/TYPES.H b/16/lib/TYPES.H
deleted file mode 100644 (file)
index 039653f..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-/*\r
- * Just some handy typedefs that make it easier to think about the low\r
- * level code\r
- */\r
-\r
-typedef unsigned char byte;\r
-typedef unsigned short word;\r
-typedef unsigned long  dword;\r
-typedef signed char sbyte;\r
-typedef signed short sword;\r
-typedef signed long sdword;\r
diff --git a/16/lib/types.h b/16/lib/types.h
deleted file mode 100644 (file)
index 039653f..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-/*\r
- * Just some handy typedefs that make it easier to think about the low\r
- * level code\r
- */\r
-\r
-typedef unsigned char byte;\r
-typedef unsigned short word;\r
-typedef unsigned long  dword;\r
-typedef signed char sbyte;\r
-typedef signed short sword;\r
-typedef signed long sdword;\r
diff --git a/16/q.bat b/16/q.bat
deleted file mode 100644 (file)
index 5d1b584..0000000
--- a/16/q.bat
+++ /dev/null
@@ -1,5 +0,0 @@
-call hres\r
-vi dos_gfx.cpp\r
-call x\r
-pause\r
-dos_gfx.exe\r
diff --git a/16/roads/ANIM.C b/16/roads/ANIM.C
new file mode 100644 (file)
index 0000000..0f3aa7e
--- /dev/null
@@ -0,0 +1,344 @@
+#define ANIM_C\r
+\r
+#include <fastgraf.h>\r
+#include <stdlib.h>\r
+#include "roads.h"\r
+#include "tiles.h"\r
+\r
+extern int far *topography;      /* BACKGROUND TILE LIST (ARRAY) */\r
+extern int far *terrain;      /* FOREGROUND TILE LIST (ARRAY) */\r
+extern int view_x, view_y;     /* VIEW AREA (UPPER LEFT CORNER) */\r
+extern int viewpage;           /* CURRENTLY VIEWED PAGE */\r
+\r
+int frogmode;\r
+int frogwatchmode=0;\r
+int animatemode=1;\r
+\r
+/* ANIMATION VARIABLES */\r
+struct ANIMATION fire, water1, water2, uranium, frog;\r
+struct ANIMATION *anim_list[ANIM_LIST_TOTAL];\r
+\r
+/*\r
+ *\r
+ * Animates all cells to the next frame.\r
+ *\r
+ */\r
+void animate (void)\r
+{\r
+    register int x, y;\r
+    int a, i, tile;\r
+    long time;\r
+    int total_updates=0;\r
+\r
+    gogofrog();\r
+    time=fg_getclock();\r
+\r
+    /* UPDATE ALL ANIM TYPES FOR NEXT FRAME IF TIME TO DO SO */\r
+    for (a=0; a<ANIM_LIST_TOTAL; a++) /* CHECK ALL ANIM TYPES */\r
+    {\r
+        if (anim_list[a]->next<1) /* EVENT_DRIVEN ANIMATION */\r
+        {\r
+            if (anim_list[a]->next==-1) /* EVENT OCCURING! */\r
+            {\r
+                anim_list[a]->next=0; /* TURN EVENT OFF */\r
+                total_updates++;\r
+            }\r
+        }\r
+\r
+        else if (anim_list[a]->next<=time) /* IS ANIM READY FOR NEXT FRAME? */\r
+        {\r
+            anim_list[a]->next=time+anim_list[a]->delay; /* SET NEXT FRAME TIME */\r
+            anim_list[a]->current+=1;\r
+            if (anim_list[a]->current>=anim_list[a]->first+anim_list[a]->total)\r
+                anim_list[a]->current=anim_list[a]->first;\r
+            total_updates++;\r
+        }\r
+    }\r
+\r
+    if (total_updates==0) return; /* NO ANIMATION TODAY.  SORRY */\r
+\r
+    /* VISUALLY UPDATE ALL VIEWABLE ANIMATIONS */\r
+    for (y=0; y<VIEW_HEIGHT; y++)\r
+    for (x=0; x<VIEW_WIDTH; x++)\r
+    {\r
+        i=VIEW_TO_WORLD(VIEW_TILE(x,y)); /* CONVERT VIEW COORDS TO WORLD */\r
+\r
+        if (is_anim(terrain[i]))\r
+        {\r
+            a=0;\r
+            while (anim_list[a]->anm!=terrain[i]) a++;\r
+            tile=anim_list[a]->current;\r
+\r
+                /* COPY TILE TO MIX THEN TO CURRENT SCREEN */\r
+            pagecopy_tile_op (topography[i], MIXING_TILE, TILEPAGE, TILEPAGE);\r
+            pagecopy_tile_tr (tile, MIXING_TILE, TILEPAGE, TILEPAGE);\r
+            pagecopy_tile_op (MIXING_TILE, VIEW_TILE(x,y), TILEPAGE, viewpage);\r
+        }\r
+    }\r
+}\r
+\r
+#define UP    1\r
+#define DOWN  2\r
+#define LEFT  3\r
+#define RIGHT 4\r
+#define CHANCE_FROG_NOT_TURN 95\r
+\r
+/*\r
+ *\r
+ * Move frog somewhere new.\r
+ *\r
+ */\r
+void gogofrog (void)\r
+{\r
+    int fails=0, r, suc;\r
+    int newx, newy;\r
+    static int x, y;\r
+    static int facing=RIGHT;\r
+    static int walking=FALSE;\r
+    static long nextfrog;\r
+\r
+    if (frogmode==3) return; /* NO FROG!  GO AWAY */\r
+\r
+    if (frogmode==2)\r
+    {\r
+        terrain[WORLD_TILE(x,y)]=EMPTY_TILE;\r
+        update_tile (WORLD_TILE(x,y));\r
+        frogmode=3;\r
+        return;\r
+    }\r
+\r
+    if (frogmode==1)\r
+    {\r
+        if (nextfrog>fg_getclock())\r
+            return; /* NOT TIME TO ANIMATE OUR FROGGIE */\r
+\r
+        frog.next=-1; /* TURN ON ANIMATION EVENT FLAG */\r
+\r
+        /* DETERMINE IF FROG CHANGES FACING */\r
+        if (random(100)>CHANCE_FROG_NOT_TURN)\r
+        {\r
+            walking=FALSE;\r
+\r
+            NEW_FACING:\r
+\r
+            /* CHANGE FACING */\r
+            r=random(100);\r
+            switch (facing)\r
+            {\r
+                case RIGHT:\r
+                case LEFT:\r
+                if (r<50) facing=UP;\r
+                else facing=DOWN;\r
+                break;\r
+\r
+                case DOWN:\r
+                case UP:\r
+                if (r<50) facing=RIGHT;\r
+                else facing=LEFT;\r
+                break;\r
+            }\r
+\r
+            /* UPDATE FACING IMAGE */\r
+            switch (facing)\r
+            {\r
+                case RIGHT: frog.current=FROG_FACE_RIGHT; break;\r
+                case LEFT:  frog.current=FROG_FACE_LEFT;  break;\r
+                case DOWN:  frog.current=FROG_FACE_DOWN;  break;\r
+                case UP:    frog.current=FROG_FACE_UP;    break;\r
+            }\r
+            nextfrog=fg_getclock()+frog.delay;\r
+            return;\r
+        }\r
+\r
+        /* DETERMINE IF FROG STARTS/STOPS WALKING */\r
+        if (walking==FALSE)\r
+        {\r
+            walking=TRUE;\r
+            switch (facing)\r
+            {\r
+                case RIGHT: frog.current=FROG_WALK_RIGHT; break;\r
+                case LEFT:  frog.current=FROG_WALK_LEFT;  break;\r
+                case DOWN:  frog.current=FROG_WALK_DOWN;  break;\r
+                case UP:    frog.current=FROG_WALK_UP;    break;\r
+            }\r
+\r
+            frog.next=-1; /* TURN ON ANIMATION EVENT FLAG */\r
+            nextfrog=fg_getclock()+frog.delay;\r
+            return;\r
+        }\r
+\r
+        /* DETERMINE IF WE CAN WALK OUR FROGGIE THIS WAY! */\r
+        newx=x;\r
+        newy=y;\r
+        switch (facing)\r
+        {\r
+            case RIGHT: newx++; break;\r
+            case LEFT:  newx--; break;\r
+            case DOWN:  newy++; break;\r
+            case UP:    newy--; break;\r
+        }\r
+\r
+        /* CAN'T MOVE -- OBSTRUCTION OR END OF WORLD! */\r
+        if (newx<0 || newy<0 || newx>=WORLD_WIDTH || newy>=WORLD_HEIGHT\r
+            || terrain[WORLD_TILE(newx,newy)]!=EMPTY_TILE)\r
+        {\r
+            /* UPDATE FACING IMAGE TO REFLECT FROG STANDS FAST */\r
+            if (random(100)<50) switch (facing)\r
+            {\r
+                case RIGHT: frog.current=FROG_FACE_RIGHT; break;\r
+                case LEFT:  frog.current=FROG_FACE_LEFT;  break;\r
+                case DOWN:  frog.current=FROG_FACE_DOWN;  break;\r
+                case UP:    frog.current=FROG_FACE_UP;    break;\r
+            }\r
+            else\r
+            {\r
+                frog.next=-1;\r
+                goto NEW_FACING;\r
+            }\r
+            nextfrog=fg_getclock()+frog.delay;\r
+            return;\r
+        }\r
+\r
+        /* CAN MOVE!  MOVE FROG ALONG */\r
+        switch (facing)\r
+        {\r
+            case RIGHT:\r
+            if (++frog.current>=FROG_NUM_WALKS+FROG_WALK_RIGHT)\r
+                frog.current=FROG_WALK_RIGHT;\r
+            break;\r
+\r
+            case LEFT:\r
+            if (++frog.current>=FROG_NUM_WALKS+FROG_WALK_LEFT)\r
+                frog.current=FROG_WALK_LEFT;\r
+            break;\r
+\r
+            case DOWN:\r
+            if (++frog.current>=FROG_NUM_WALKS+FROG_WALK_DOWN)\r
+                frog.current=FROG_WALK_DOWN;\r
+            break;\r
+\r
+            case UP:\r
+            if (++frog.current>=FROG_NUM_WALKS+FROG_WALK_UP)\r
+                frog.current=FROG_WALK_UP;\r
+            break;\r
+\r
+        }\r
+\r
+            /* DON'T MOVE FROG'S X/Y AT CERTAIN FRAMES */\r
+        if (frog.current==FROG_WALK_UP+1 ||\r
+            frog.current==FROG_WALK_UP+3 ||\r
+            frog.current==FROG_WALK_LEFT+1 ||\r
+            frog.current==FROG_WALK_LEFT+3 ||\r
+            frog.current==FROG_WALK_RIGHT+1 ||\r
+            frog.current==FROG_WALK_RIGHT+3 ||\r
+            frog.current==FROG_WALK_DOWN+1 ||\r
+            frog.current==FROG_WALK_DOWN+3 )\r
+        {\r
+            frog.next=-1;\r
+            nextfrog=fg_getclock()+frog.delay;\r
+            return;\r
+        }\r
+\r
+        terrain[WORLD_TILE(x,y)]=EMPTY_TILE;\r
+        terrain[WORLD_TILE(newx,newy)]=ANM_FROG;\r
+        frog.next=-1;\r
+\r
+/*\r
+ *\r
+ * Simply put, frog watch mode doesn't work.\r
+ * I got to the point where I said, gosh darnit, who needs\r
+ * to watch a stinking frog anyway?  It's left as is...\r
+ *\r
+ */\r
+        if (frogwatchmode)\r
+        {\r
+\r
+            if (newx>x)      if (view_x<WORLD_WIDTH-view_x) view_x++;\r
+            else if (newx<x) if (view_x>0) view_x--;\r
+            else if (newy>y) if (view_y<WORLD_HEIGHT-view_y) view_y++;\r
+            else if (newy<y) if (view_y>0) view_y--;\r
+            redraw (NONFLIP_REFRESH);\r
+/*            if (newx>x)      suc=redraw (NONFLIP_SCROLL_RIGHT);\r
+            else if (newx<x) suc=redraw (NONFLIP_SCROLL_LEFT);\r
+            else if (newy>y) suc=redraw (NONFLIP_SCROLL_DOWN);\r
+            else if (newy<y) suc=redraw (NONFLIP_SCROLL_UP);*/\r
+            viewpage=!viewpage; /* SWAP PAGES */\r
+            update_tile (WORLD_TILE(x,y));\r
+            fg_setvpage (viewpage); /* VIEW CORRECT PAGE */\r
+        } else update_tile (WORLD_TILE(x,y));\r
+\r
+        x=newx;\r
+        y=newy;\r
+        nextfrog=fg_getclock()+frog.delay;\r
+        return;\r
+    }\r
+\r
+/********************* ADD NEW FROG ************************/\r
+\r
+    /* LOCATE VIEWABLE TILE TO PLACE FROG */\r
+    do {\r
+        x=random(VIEW_WIDTH)+view_x;\r
+        y=random(VIEW_HEIGHT)+view_y;\r
+        fails++;\r
+    } while (terrain[WORLD_TILE(x,y)]!=EMPTY_TILE && fails<50);\r
+\r
+    if (fails>=50) /* COULDN'T PLACE FROG */\r
+    {\r
+        fg_music ("L64EC.DE.C$");\r
+        frogmode=3;\r
+        frog.next=0;\r
+        return;\r
+    }\r
+\r
+    terrain[WORLD_TILE(x,y)]=ANM_FROG; /* INSTALL FROG! */\r
+    frog.next=-1; /* UPDATE EVENT */\r
+    frogmode=1;\r
+    nextfrog=fg_getclock()+frog.delay;\r
+\r
+    return;\r
+}\r
+\r
+/*\r
+ *\r
+ * Initializes any animation information, as in at the program start.\r
+ *\r
+ */\r
+void init_anim (void)\r
+{\r
+    fire.first=fire.current=34;\r
+    fire.total=6;\r
+    fire.delay=1;\r
+    fire.next=fg_getclock()+fire.delay;\r
+    fire.anm=ANM_FIRE;\r
+\r
+    water1.first=water1.current=60;\r
+    water1.total=3;\r
+    water1.delay=3;\r
+    water1.next=fg_getclock()+water1.delay;\r
+    water1.anm=ANM_WATER1;\r
+\r
+    water2.first=water2.current=63;\r
+    water2.total=3;\r
+    water2.delay=4;\r
+    water2.next=fg_getclock()+water2.delay;\r
+    water2.anm=ANM_WATER2;\r
+\r
+    uranium.first=uranium.current=66;\r
+    uranium.total=10;\r
+    uranium.delay=4;\r
+    uranium.next=fg_getclock()+uranium.delay;\r
+    uranium.anm=ANM_URANIUM;\r
+\r
+    frog.first=frog.current=FROG_FACE_RIGHT;\r
+    frog.total=FROG_NUM_WALKS;\r
+    frog.delay=3;\r
+    frog.next=-1;\r
+    frog.anm=ANM_FROG;\r
+\r
+    anim_list[0]=&fire;\r
+    anim_list[1]=&water1;\r
+    anim_list[2]=&water2;\r
+    anim_list[3]=&uranium;\r
+    anim_list[4]=&frog;\r
+}\r
+\r
diff --git a/16/roads/BOOKENDS.C b/16/roads/BOOKENDS.C
new file mode 100644 (file)
index 0000000..0daa74a
--- /dev/null
@@ -0,0 +1,97 @@
+#define BOOKENDS_C\r
+\r
+#include <stdio.h>\r
+#include <fastgraf.h>\r
+#include <alloc.h>\r
+#include <stdlib.h>\r
+#include "roads.h"\r
+#include "fx.h"         /* FOR FADING */\r
+#include "version.h"    /* FOR HEADER */\r
+\r
+extern int far *topography;      /* BACKGROUND TILE LIST (ARRAY) */\r
+extern int far *terrain;      /* FOREGROUND TILE LIST (ARRAY) */\r
+extern int viewpage;           /* CURRENTLY VIEWED PAGE */\r
+\r
+int startup_vmode=-1;   /* STARTUP VIDEO MODE TO RETURN TO AT PROGRAM END */\r
+\r
+/*\r
+ *\r
+ * Initializes/Sets-up the video mode and paging.\r
+ *\r
+ */\r
+void init_video (void)\r
+{\r
+                       /* INITIALIZE VIDEO MODE */\r
+    if (fg_testmode (VMODE, VPAGES)==0)\r
+    {\r
+        printf ("Sorry, your video card does not support mode %d.\n", VMODE);\r
+        exit (1);\r
+    }\r
+\r
+    startup_vmode=fg_getmode();\r
+    fg_setcolor(0);\r
+    fg_setmode (VMODE);\r
+    fg_erase();\r
+\r
+        /* SETUP SPRITE IMAGES */\r
+    fg_setpage (TILEPAGE);\r
+    fg_erase();\r
+    fg_showgif (IMAGES, 0);\r
+    fg_setpage (viewpage);\r
+    fg_erase();\r
+    fg_tcdefine (0,1); /* TREAT COLOR 0 AS TRANSPARENT */\r
+}\r
+\r
+/*\r
+ *\r
+ * "Officially" shuts down the program.  Restores video, frees\r
+ * allocated data including fonts, unhooks Sound Blaster,\r
+ * and (optionally) exits with a message and errorcode\r
+ *\r
+ */\r
+void program_shutdown (char *msg, int errcode)\r
+{\r
+    fg_kbinit(0); /* UNLATCH LOW-LEVEL KEYBOARD HANDLER */\r
+\r
+        /* FREE DATA */\r
+    if (topography!=NULL) farfree (topography);\r
+    if (terrain!=NULL) farfree (terrain);\r
+\r
+        /* RESTORE ORIGINAL VIDEO MODE TO USER */\r
+    if (startup_vmode!=-1)\r
+    {\r
+        fade_out_all ();\r
+        fg_setmode (startup_vmode);\r
+        fg_reset();\r
+    }\r
+\r
+        /* FREE FONTS AND SOUND BLASTER SOMEWHERE IN HERE */\r
+\r
+    printf (HEADER); /* PRINT HEADER */\r
+\r
+               /* REPORT MESSAGE */\r
+    if (*msg!=NULL)\r
+        printf ("ROADS:  %s\n", msg);\r
+\r
+        /* QUIT WITH ERROR CODE, IF SUPPLIED ONE */\r
+    if (errcode!=-1)\r
+               exit (errcode);\r
+\r
+        /* OTHERWISE, RETURN TO CALLER WHO WILL HANDLE EXITING */\r
+    else return;\r
+}\r
+\r
+/*\r
+ *\r
+ * Initialize dynamic data, which is freed with program_shutdown()\r
+ *\r
+ */\r
+void init_data (void)\r
+{\r
+    topography=farcalloc (WORLD_TILES_TOTAL,sizeof (int));\r
+    terrain=farcalloc (WORLD_TILES_TOTAL,sizeof (int));\r
+\r
+    if (topography==NULL || terrain==NULL)\r
+       program_shutdown ("Not enough memory -- It's not my fault, I swear!", 1);\r
+}\r
+\r
diff --git a/16/roads/FX.C b/16/roads/FX.C
new file mode 100644 (file)
index 0000000..034c623
--- /dev/null
@@ -0,0 +1,95 @@
+#define FX_C\r
+\r
+#include <fastgraf.h> /* FOR PALETTE AND KEYHIT FUNCTIONS */\r
+#include "fx.h"\r
+#include "roads.h"    /* FOR ANIMATE FUNCTION  */\r
+\r
+/* FX.C-specific DEFINITIONS */\r
+#define FADESTEPS 32     /* Number of gradations in the fade -- KEEP BELOW 256 */\r
+#define SETDACS_DELAY 0  /* Number of clock ticks to wait between gradations */\r
+\r
+/****************************************************************************\\r
+*                                                                            *\r
+*  fade_in                                                                   *\r
+*                                                                            *\r
+*  Fade one or more DACs from black to their target colors.                  *\r
+*                                                                            *\r
+\****************************************************************************/\r
+\r
+void fade_in (int DACstart, int DACend)\r
+{\r
+   register int j, i;\r
+   int k, n, temp;\r
+   char new_palette [VCOLORS*3]; /* Temporarily stores palette */\r
+   char key1, key2; /* USED FOR FOR KEYCHECK */\r
+\r
+   if (DACend<DACstart) /* PREVENT start/end REVERSALS which could crash us */\r
+   {\r
+      temp=DACstart;\r
+      DACstart=DACend;\r
+      DACend=temp;\r
+   }\r
+\r
+   for (i = 0; i <= FADESTEPS; i++)\r
+   {\r
+      if (abortfadeonkeyhit)\r
+      {\r
+        fg_intkey (&key1, &key2);\r
+        if (key1+key2>0) break;\r
+      }\r
+\r
+      for (k=0, n=DACstart*3, j = DACstart; j <= DACend; j++)\r
+      {\r
+         new_palette[k++] = (long) (default_palette[n++] * i) / FADESTEPS;\r
+         new_palette[k++] = (long) (default_palette[n++] * i) / FADESTEPS;\r
+         new_palette[k++] = (long) (default_palette[n++] * i) / FADESTEPS;\r
+      }\r
+\r
+      fg_setdacs (DACstart, DACend-DACstart+1, new_palette);\r
+      fg_waitfor (SETDACS_DELAY);\r
+      if (animatewhilefading) animate();\r
+   }\r
+}\r
+\r
+/****************************************************************************\\r
+*                                                                            *\r
+*  fade_out                                                                  *\r
+*                                                                            *\r
+*  Fade one or more DACs from their current colors to black.                 *\r
+*                                                                            *\r
+\****************************************************************************/\r
+\r
+void fade_out (int DACstart, int DACend)\r
+{\r
+   register int j, i;\r
+   int k, n, temp;\r
+   char new_palette [VCOLORS*3]; /* Temporarily stores palette */\r
+   char key1, key2; /* USED FOR FOR KEYCHECK */\r
+\r
+   if (DACend<DACstart) /* PREVENT start/end REVERSALS */\r
+   {\r
+      temp=DACstart;\r
+      DACstart=DACend;\r
+      DACend=temp;\r
+   }\r
+\r
+   for (i = FADESTEPS; i >= 0; i--)\r
+   {\r
+      if (abortfadeonkeyhit)\r
+      {\r
+        fg_intkey (&key1, &key2);\r
+        if (key1+key2>0) break;\r
+      }\r
+\r
+      for (k=0, n=DACstart*3, j = DACstart; j <= DACend; j++)\r
+      {\r
+         new_palette[k++] = (long) (default_palette[n++] * i) / FADESTEPS;\r
+         new_palette[k++] = (long) (default_palette[n++] * i) / FADESTEPS;\r
+         new_palette[k++] = (long) (default_palette[n++] * i) / FADESTEPS;\r
+      }\r
+      fg_setdacs (DACstart, DACend-DACstart+1, new_palette);\r
+      fg_waitfor (SETDACS_DELAY);\r
+      if (animatewhilefading) animate();\r
+   }\r
+}\r
+\r
diff --git a/16/roads/FX.H b/16/roads/FX.H
new file mode 100644 (file)
index 0000000..ca98a7f
--- /dev/null
@@ -0,0 +1,32 @@
+#ifndef __MEM_H\r
+#include <mem.h>\r
+#endif\r
+\r
+/* DEFINITIONS */\r
+#define VCOLORS     256         /* Number of screen colors */\r
+\r
+/* GLOBAL VARIABLES */\r
+#ifdef FX_C\r
+char default_palette[VCOLORS*3]; /* Stores the palette before fades */\r
+char empty_palette[VCOLORS*3];   /* Stores an empty palette for quick clearing */\r
+char abortfadeonkeyhit=0;        /* Quit fading on keyhit? */\r
+char animatewhilefading=1;       /* Animate screen while performing fade? */\r
+#else\r
+extern char default_palette[VCOLORS*3];\r
+extern char empty_palette[VCOLORS*3];\r
+extern char abortfadeonkeyhit;\r
+extern char animatewhilefading;\r
+#endif\r
+\r
+/* PROTOTYPES */\r
+void fade_in (int DACstart, int DACend);\r
+void fade_out (int DACstart, int DACend);\r
+\r
+/* MACROS */\r
+#define fade_init()     fg_getdacs (0, VCOLORS, default_palette); /* COPY PALETTE */\r
+#define fade_blackout() memset (empty_palette, 0, VCOLORS*3);\\r
+                        fg_setdacs(0, VCOLORS, empty_palette);    /* SET DACS TO ZERO */\r
+\r
+#define fade_out_all() fade_out(0,255)\r
+#define fade_in_all()  fade_in(0,255)\r
+\r
diff --git a/16/roads/INITROAD.C b/16/roads/INITROAD.C
new file mode 100644 (file)
index 0000000..c83effe
--- /dev/null
@@ -0,0 +1,467 @@
+#define INITROAD_C\r
+\r
+#include <stdlib.h>\r
+#include <time.h>\r
+#include <fastgraf.h>\r
+#include "roads.h"\r
+#include "tiles.h"\r
+\r
+extern int far *topography;      /* BACKGROUND TILE LIST (ARRAY) */\r
+extern int far *terrain;      /* FOREGROUND TILE LIST (ARRAY) */\r
+\r
+/* FOR AI - NUMBER OF ROADS TO PLACE ON SCREEN */\r
+#define MAX_ROADS ((WORLD_WIDTH+WORLD_HEIGHT)/2)\r
+#define MIN_ROADS ((WORLD_WIDTH+WORLD_HEIGHT)/8)\r
+#define CHANCE_CROSSROAD 75 /* 3/4 CHANCE OF A CROSSROAD VS T */\r
+\r
+/* DIRECTIONS */\r
+#define NUM_DIRECTIONS  4\r
+#define FIRST_DIRECTION 1\r
+\r
+#define DIRECTION_UP    1\r
+#define DIRECTION_DOWN  2\r
+#define DIRECTION_LEFT  3\r
+#define DIRECTION_RIGHT 4\r
+\r
+#define ROAD_LEADERS 12 /* USED IN CREATE_ROADS */\r
+\r
+/*\r
+ *\r
+ * Randomly creates roads and places them in the foreground tile list.\r
+ * This function overwrites other non-road foreground tiles.\r
+ * (To prevent this, call this function first.)\r
+ *\r
+ */\r
+void create_roads (void)\r
+{\r
+    int entry, keepgoing=TRUE, direction, chance;\r
+    int x,y,nextx,nexty; /* LOCATION OF CURRENTLY PLACED ROAD TILE */\r
+    int roads_left; /* NUMBER OF COMPLETE ROADS LEFT TO PLACE */\r
+    int failed_roads=0;\r
+    int current_tile;\r
+\r
+               /* ROADS ARE PICKED FROM THESE LISTS -- INCREASE ROAD_LEADERS  */\r
+        /* AND ADD ROAD TILES TO THESE LISTS TO INCREASE CHANCE OF     */\r
+        /* SPECIFIED ROADS BEING PLACED                                */\r
+    int roads_to_right [ROAD_LEADERS] = { ROAD_H, ROAD_H, ROAD_UR, ROAD_DR,\r
+                                          ROAD_H, ROAD_H, ROAD_H, ROAD_H,\r
+                                          ROAD_H, ROAD_H, ROAD_H, ROAD_H,  };\r
+    int roads_to_left  [ROAD_LEADERS] = { ROAD_H, ROAD_H, ROAD_UL, ROAD_DL,\r
+                                          ROAD_H, ROAD_H, ROAD_H, ROAD_H,\r
+                                          ROAD_H, ROAD_H, ROAD_H, ROAD_H,  };\r
+    int roads_to_down  [ROAD_LEADERS] = { ROAD_V, ROAD_V, ROAD_UL, ROAD_UR,\r
+                                          ROAD_V, ROAD_V, ROAD_V, ROAD_V,\r
+                                          ROAD_V, ROAD_V, ROAD_V, ROAD_V,  };\r
+    int roads_to_up    [ROAD_LEADERS] = { ROAD_V, ROAD_V, ROAD_DL, ROAD_DR,\r
+                                                                                 ROAD_V, ROAD_V, ROAD_V, ROAD_V,\r
+                                          ROAD_V, ROAD_V, ROAD_V, ROAD_V,  };\r
+\r
+    roads_left=random (MAX_ROADS-MIN_ROADS)+MIN_ROADS;\r
+\r
+        /************************************************\\r
+         *     PLACE FIRST TILE AT WORLD SCREEN EDGE    *\r
+        \************************************************/\r
+\r
+    while (roads_left-- && failed_roads<=MAX_FAILS)\r
+    {\r
+            /* PICK RANDOM ENTRY POINT */\r
+        keepgoing=TRUE;\r
+        entry=random(NUM_DIRECTIONS)+FIRST_DIRECTION;\r
+        switch (entry)\r
+        {\r
+/********/  case DIRECTION_UP: /* TOP ENTRY */\r
+\r
+            x=random(WORLD_WIDTH); y=0;\r
+            current_tile=terrain[WORLD_TILE(x,y)];\r
+\r
+            if (!isroad(current_tile))\r
+            {\r
+                current_tile=terrain[WORLD_TILE(x,y)]=\r
+                    roads_to_up[random(ROAD_LEADERS)];\r
+                direction=roadexit(current_tile, DIRECTION_DOWN);\r
+                               break;\r
+            }\r
+\r
+            else\r
+            {\r
+                roads_left++;\r
+                failed_roads++;\r
+                keepgoing=FALSE;\r
+                break;\r
+            }\r
+\r
+/********/  case DIRECTION_DOWN: /* BOTTOM ENTRY */\r
+\r
+                       x=random(WORLD_WIDTH); y=WORLD_HEIGHT-1;\r
+            current_tile=terrain[WORLD_TILE(x,y)];\r
+\r
+            if (!isroad(current_tile))\r
+            {\r
+                current_tile=terrain[WORLD_TILE(x,y)]=\r
+                    roads_to_down[random(ROAD_LEADERS)];\r
+                direction=roadexit(current_tile, DIRECTION_UP);\r
+            }\r
+\r
+            else\r
+            {\r
+                roads_left++;\r
+                               failed_roads++;\r
+                keepgoing=FALSE;\r
+            }\r
+            break;\r
+\r
+/********/  case DIRECTION_LEFT: /* LEFT ENTRY */\r
+\r
+            x=0; y=random(WORLD_HEIGHT);\r
+            current_tile=terrain[WORLD_TILE(x,y)];\r
+\r
+            if (!isroad(current_tile))\r
+            {\r
+                current_tile=terrain[WORLD_TILE(x,y)]=\r
+                                       roads_to_left[random(ROAD_LEADERS)];\r
+                direction=roadexit(current_tile, DIRECTION_RIGHT);\r
+            }\r
+\r
+            else\r
+            {\r
+                roads_left++;\r
+                failed_roads++;\r
+                keepgoing=FALSE;\r
+            }\r
+            break;\r
+\r
+/********/  case DIRECTION_RIGHT: /* RIGHT ENTRY */\r
+\r
+            x=WORLD_WIDTH-1; y=random(WORLD_HEIGHT);\r
+            current_tile=terrain[WORLD_TILE(x,y)];\r
+\r
+            if (!isroad(current_tile))\r
+            {\r
+                current_tile=terrain[WORLD_TILE(x,y)]=\r
+                    roads_to_right[random(ROAD_LEADERS)];\r
+                direction=roadexit(current_tile, DIRECTION_LEFT);\r
+            }\r
+\r
+            else\r
+            {\r
+                               roads_left++;\r
+                failed_roads++;\r
+                keepgoing=FALSE;\r
+            }\r
+            break;\r
+        }\r
+\r
+        /************************************************\\r
+         * PLACE SUBSEQUENT TILES AWAY FROM ENTRY POINT *\r
+        \************************************************/\r
+\r
+        while (keepgoing)\r
+        {\r
+                       switch (direction)\r
+            {\r
+/********/      case DIRECTION_UP: /* UP */\r
+\r
+                if (--y<0)\r
+                {\r
+                    keepgoing=FALSE;\r
+                    break;\r
+                }\r
+\r
+                current_tile=terrain[WORLD_TILE(x,y)];\r
+                if (!isroad(current_tile))\r
+                {\r
+                    current_tile=terrain[WORLD_TILE(x,y)]=\r
+                        roads_to_down[random(ROAD_LEADERS)];\r
+                    direction=roadexit(current_tile, DIRECTION_UP);\r
+                }\r
+\r
+                else /* INTERSECTION OCCURS */\r
+                {\r
+                    if (random(100)>=CHANCE_CROSSROAD &&\r
+                        terrain[WORLD_TILE(x,y-1)]==EMPTY_TILE)\r
+                    {\r
+                        keepgoing=FALSE;\r
+                        terrain[WORLD_TILE(x,y)]=makeintersection\r
+                            (current_tile, DIRECTION_DOWN);\r
+                                       }\r
+\r
+                    else /* CROSSROAD AND CONTINUE */\r
+                    {\r
+                        terrain[WORLD_TILE(x,y)]=makeintersection\r
+                            (makeintersection(current_tile, DIRECTION_UP),\r
+                            DIRECTION_DOWN); /* ADD BOTH RAMPS */\r
+                    }\r
+                }\r
+                break;\r
+\r
+/********/      case DIRECTION_DOWN:  /* DOWN */\r
+\r
+                               if (++y>=WORLD_HEIGHT)\r
+                {\r
+                    keepgoing=FALSE;\r
+                    break;\r
+                }\r
+\r
+                current_tile=terrain[WORLD_TILE(x,y)];\r
+                if (!isroad(current_tile))\r
+                {\r
+                    current_tile=terrain[WORLD_TILE(x,y)]=\r
+                        roads_to_up[random(ROAD_LEADERS)];\r
+                    direction=roadexit(current_tile, DIRECTION_DOWN);\r
+                }\r
+\r
+                else /* INTERSECTION OCCURS */\r
+                {\r
+                    if (random(100)>=CHANCE_CROSSROAD &&\r
+                        terrain[WORLD_TILE(x,y+1)]==EMPTY_TILE)\r
+\r
+                    {\r
+                        keepgoing=FALSE;\r
+                        terrain[WORLD_TILE(x,y)]=makeintersection\r
+                            (current_tile, DIRECTION_UP);\r
+                    }\r
+\r
+                    else /* CROSSROAD AND CONTINUE */\r
+                                       {\r
+                        terrain[WORLD_TILE(x,y)]=makeintersection\r
+                            (makeintersection(current_tile, DIRECTION_DOWN),\r
+                            DIRECTION_UP); /* ADD BOTH RAMPS */\r
+                    }\r
+                }\r
+                break;\r
+\r
+/********/      case DIRECTION_LEFT: /* LEFT */\r
+\r
+                if (--x<0)\r
+                {\r
+                    keepgoing=FALSE;\r
+                                       break;\r
+                }\r
+\r
+                current_tile=terrain[WORLD_TILE(x,y)];\r
+                if (!isroad(current_tile))\r
+                {\r
+                    current_tile=terrain[WORLD_TILE(x,y)]=\r
+                        roads_to_right[random(ROAD_LEADERS)];\r
+                    direction=roadexit(current_tile, DIRECTION_LEFT);\r
+                }\r
+\r
+                else /* INTERSECTION OCCURS */\r
+                {\r
+                                       if (random(100)>=CHANCE_CROSSROAD &&\r
+                        terrain[WORLD_TILE(x-1,y)]==EMPTY_TILE)\r
+\r
+                    {\r
+                        keepgoing=FALSE;\r
+                        terrain[WORLD_TILE(x,y)]=makeintersection\r
+                            (current_tile, DIRECTION_RIGHT);\r
+                    }\r
+\r
+                    else /* CROSSROAD AND CONTINUE */\r
+                    {\r
+                        terrain[WORLD_TILE(x,y)]=makeintersection\r
+                            (makeintersection(current_tile, DIRECTION_LEFT),\r
+                                                       DIRECTION_RIGHT); /* ADD BOTH RAMPS */\r
+                    }\r
+                }\r
+                break;\r
+\r
+/********/      case DIRECTION_RIGHT: /* RIGHT */\r
+\r
+                if (++x>=WORLD_WIDTH)\r
+                {\r
+                    keepgoing=FALSE;\r
+                    break;\r
+                }\r
+\r
+                current_tile=terrain[WORLD_TILE(x,y)];\r
+                if (!isroad(current_tile))\r
+                {\r
+                    current_tile=terrain[WORLD_TILE(x,y)]=\r
+                        roads_to_left[random(ROAD_LEADERS)];\r
+                    direction=roadexit(current_tile, DIRECTION_RIGHT);\r
+                }\r
+\r
+                else /* INTERSECTION OCCURS */\r
+                {\r
+                    if (random(100)>=CHANCE_CROSSROAD &&\r
+                        terrain[WORLD_TILE(x+1,y)]==EMPTY_TILE)\r
+\r
+                                       {\r
+                        keepgoing=FALSE;\r
+                        terrain[WORLD_TILE(x,y)]=makeintersection\r
+                            (current_tile, DIRECTION_LEFT);\r
+                    }\r
+\r
+                    else /* CROSSROAD AND CONTINUE */\r
+                    {\r
+                        terrain[WORLD_TILE(x,y)]=makeintersection\r
+                            (makeintersection(current_tile, DIRECTION_RIGHT),\r
+                            DIRECTION_LEFT); /* ADD BOTH RAMPS */\r
+                    }\r
+                }\r
+                               break;\r
+            } /* "DIRECTION" HERE */\r
+        } /* STOP "KEEPGOING" HERE */\r
+    }\r
+}\r
+\r
+/*\r
+ *\r
+ * Returns the unspecified direction in an angled road.\r
+ *\r
+ */\r
+int roadexit (int road, int direction)\r
+{\r
+       switch (direction)\r
+    {\r
+        case DIRECTION_UP: /* up */\r
+        if (road==ROAD_V)  return DIRECTION_UP;\r
+        if (road==ROAD_UL) return DIRECTION_LEFT;\r
+        if (road==ROAD_UR) return DIRECTION_RIGHT;\r
+        break;\r
+\r
+        case DIRECTION_DOWN: /* down */\r
+        if (road==ROAD_V)  return DIRECTION_DOWN;\r
+        if (road==ROAD_DL) return DIRECTION_LEFT;\r
+        if (road==ROAD_DR) return DIRECTION_RIGHT;\r
+        break;\r
+\r
+        case DIRECTION_LEFT: /* left */\r
+        if (road==ROAD_DR) return DIRECTION_UP;\r
+        if (road==ROAD_UR) return DIRECTION_DOWN;\r
+        if (road==ROAD_H)  return DIRECTION_LEFT;\r
+        break;\r
+\r
+        case DIRECTION_RIGHT: /* right */\r
+        if (road==ROAD_DL) return DIRECTION_UP;\r
+        if (road==ROAD_UL) return DIRECTION_DOWN;\r
+        if (road==ROAD_H)  return DIRECTION_RIGHT;\r
+        break;\r
+    }\r
+\r
+    fg_music ("A$");\r
+    return ERROR_TILE;\r
+}\r
+\r
+/*\r
+ *\r
+ * Adds a road (ramp) to the specified road, and returns the\r
+ * tile number of what the new road is made of.\r
+ *\r
+ */\r
+int makeintersection (int road, int ramp)\r
+{\r
+       switch (road)\r
+    {\r
+        case ROAD_X:  /* Å */\r
+        return ROAD_X;\r
+\r
+        case ROAD_V:  /* ³ */\r
+        if (ramp==DIRECTION_LEFT) return ROAD_TL;\r
+        if (ramp==DIRECTION_RIGHT) return ROAD_TR;\r
+        return ROAD_V;\r
+\r
+        case ROAD_H:  /* Ä */\r
+        if (ramp==DIRECTION_UP) return ROAD_TU;\r
+        if (ramp==DIRECTION_DOWN) return ROAD_TD;\r
+               return ROAD_H;\r
+\r
+        case ROAD_UR: /* Ú */\r
+        if (ramp==DIRECTION_UP) return ROAD_TR;\r
+        if (ramp==DIRECTION_LEFT) return ROAD_TD;\r
+        return ROAD_UR;\r
+\r
+        case ROAD_UL: /* ¿ */\r
+        if (ramp==DIRECTION_UP) return ROAD_TL;\r
+        if (ramp==DIRECTION_RIGHT) return ROAD_TD;\r
+        return ROAD_UL;\r
+\r
+        case ROAD_DR: /* À */\r
+               if (ramp==DIRECTION_DOWN) return ROAD_TR;\r
+        if (ramp==DIRECTION_LEFT) return ROAD_TU;\r
+        return ROAD_DR;\r
+\r
+        case ROAD_DL: /* Ù */\r
+        if (ramp==DIRECTION_DOWN) return ROAD_TL;\r
+        if (ramp==DIRECTION_RIGHT) return ROAD_TU;\r
+        return ROAD_DL;\r
+\r
+        case ROAD_TL: /* ´ */\r
+        if (ramp==DIRECTION_RIGHT) return ROAD_X;\r
+        return ROAD_TL;\r
+\r
+               case ROAD_TR: /* Ã */\r
+        if (ramp==DIRECTION_LEFT) return ROAD_X;\r
+        return ROAD_TR;\r
+\r
+        case ROAD_TU: /* Á */\r
+        if (ramp==DIRECTION_DOWN) return ROAD_X;\r
+        return ROAD_TU;\r
+\r
+        case ROAD_TD: /* Â */\r
+        if (ramp==DIRECTION_UP) return ROAD_X;\r
+        return ROAD_TD;\r
+    }\r
+\r
+       fg_music ("A$");\r
+    return ERROR_TILE;\r
+}\r
+\r
+/*\r
+    AI USED IN ROAD FUNCTIONS:\r
+    pick random entry point on one of the four sides\r
+    place subsequent tiles with tendency to move from screen\r
+    place subsequent tiles with tendency use V or H pieces if just used\r
+    if hit existing tile, either\r
+    a) cross over road and continue, using X tile\r
+    b) terminate road with a T\r
+    repeat until MAX_ROADS complete\r
+*/\r
+\r
+/*\r
+ *\r
+ * Adds edges to places where grass and dirt meet.\r
+ * Called by init_background() to liven up background display.\r
+ *\r
+ */\r
+void add_dirt_edges (void)\r
+{\r
+    register int x, y;\r
+    int tile;\r
+\r
+    /* ADD 90 DEGREE EDGES */\r
+    for (y=0; y<WORLD_HEIGHT; y++)\r
+    for (x=0; x<WORLD_WIDTH; x++)\r
+    if (isdirt(topography[WORLD_TILE(x,y)]))\r
+    {\r
+        tile=0; /* ADD UP BINARY TILE NUMBER */\r
+        if (y-1>=0) tile+=isgrass(topography[WORLD_TILE(x,y-1)]);\r
+        if (x+1<WORLD_WIDTH) tile+=isgrass(topography[WORLD_TILE(x+1,y)])*2;\r
+        if (y+1<WORLD_HEIGHT) tile+=isgrass(topography[WORLD_TILE(x,y+1)])*4;\r
+        if (x-1>=0) tile+=isgrass(topography[WORLD_TILE(x-1,y)])*8;\r
+\r
+        /* CONVERT BINARY TILE NUMBER TO ACTUAL */\r
+        switch (tile)\r
+        {\r
+            case 1:  tile=DIRTEDGE_U;    break;\r
+            case 2:  tile=DIRTEDGE_R;    break;\r
+            case 3:  tile=DIRTEDGE_UR;   break;\r
+            case 4:  tile=DIRTEDGE_D;    break;\r
+            case 5:  tile=DIRTEDGE_UD;   break;\r
+            case 6:  tile=DIRTEDGE_RD;   break;\r
+            case 7:  tile=DIRTEDGE_URD;  break;\r
+            case 8:  tile=DIRTEDGE_L;    break;\r
+            case 9:  tile=DIRTEDGE_UL;   break;\r
+            case 10: tile=DIRTEDGE_RL;   break;\r
+            case 11: tile=DIRTEDGE_URL;  break;\r
+            case 12: tile=DIRTEDGE_DL;   break;\r
+            case 13: tile=DIRTEDGE_UDL;  break;\r
+            case 14: tile=DIRTEDGE_RDL;  break;\r
+            case 15: tile=DIRTEDGE_URDL; break;\r
+            case 0:  tile=topography[WORLD_TILE(x,y)]; break; /* NO CHANGE */\r
+        }\r
+        topography[WORLD_TILE(x,y)]=tile; /* CHANGE TILE */\r
+    }\r
+}\r
+\r
diff --git a/16/roads/INITW.C b/16/roads/INITW.C
new file mode 100644 (file)
index 0000000..9501d47
--- /dev/null
@@ -0,0 +1,153 @@
+#define INITW_C\r
+\r
+#include <stdlib.h>\r
+#include <time.h>\r
+#include "roads.h"\r
+#include "tiles.h"\r
+\r
+extern int view_x, view_y;   /* VIEW AREA (UPPER LEFT CORNER) */\r
+extern int viewpage;         /* CURRENTLY VIEWED PAGE */\r
+extern int startup_vmode;    /* VIDEO MODE STARTUP SETTINGS, -1 IF NOT INIT */\r
+\r
+int far *topography;      /* BACKGROUND TILE LIST (ARRAY) */\r
+int far *terrain;      /* FOREGROUND TILE LIST (ARRAY) */\r
+int world_type=75;      /* TENDENCY TO GRASS */\r
+int edgemode=1;         /* BLOCKY GRASS/DIRT OR EDGED? */\r
+extern int frogmode;\r
+\r
+/*\r
+ *\r
+ * Loads the world foreground tile list with roads.\r
+ *\r
+ */\r
+void init_foreground(void)\r
+{\r
+    register int x, tile, fails;\r
+\r
+        /* INITIALIZE FOREGROUND */\r
+    for (x=0; x<WORLD_TILES_TOTAL; x++)\r
+        terrain[x]=EMPTY_TILE; /* FILL WITH EMPTY TILES */\r
+\r
+    create_roads (); /* LAY DOWN SOME ROADS */\r
+\r
+        /* ADD RANDOM TERRAIN */\r
+    for (x=0; x<random(MAX_TERRAIN-MIN_TERRAIN)+MIN_TERRAIN; x++)\r
+    {\r
+        fails=0;\r
+\r
+            /* ATTEMPT TO PLACE TERRAIN -- QUIT ON EXCESSIVE FAILURE */\r
+        do {\r
+            tile=random(WORLD_TILES_TOTAL);\r
+            fails++;\r
+        } while (terrain[tile]!=EMPTY_TILE && fails<MAX_FAILS);\r
+\r
+        if (fails<MAX_FAILS)\r
+        {\r
+            switch (random(12)+1)\r
+            {\r
+                case 1: terrain[tile]=OBJ_SIGN;     break;\r
+                case 2: terrain[tile]=OBJ_FIRST_GEM+random(OBJ_TOTAL_GEM); break;\r
+                case 3: terrain[tile]=ANM_URANIUM;  break;\r
+                case 4:\r
+                case 5: terrain[tile]=ANM_FIRE;     break;\r
+                case 6: if (random(100)<50) terrain[tile]=ANM_WATER1;\r
+                        else terrain[tile]=ANM_WATER2;  break;\r
+                case 7: terrain[tile]=OBJ_ROCK1;    break;\r
+                case 8: terrain[tile]=OBJ_ROCK2;    break;\r
+                case 9: terrain[tile]=OBJ_ROCK3;    break;\r
+                case 10:\r
+                case 11:\r
+                case 12: terrain[tile]=OBJ_BUSHES;  break;\r
+            }\r
+        }\r
+\r
+        else break;\r
+    }\r
+\r
+    frogmode=3;\r
+}\r
+\r
+/*\r
+ *\r
+ * Loads the world background tile list with grassy tiles.\r
+ *\r
+ */\r
+void init_background(void)\r
+{\r
+    register int x, y, landtype_left, landtype_up, landtype_here;\r
+\r
+        /* INITIALIZE BACKGROUND BY RANDOMLY PICKING TILES */\r
+\r
+    /* DO FIRST TILE */\r
+    topography[0]=random(NUM_LAND_TILES)+FIRST_LAND_TILE;\r
+\r
+    /* DO FIRST ROW */\r
+    for (x=1; x<WORLD_WIDTH; x++)\r
+    {\r
+        landtype_left=isdirt(topography[x-1]);\r
+        landtype_here=landtype_left;\r
+        if (random(100)>=CHANCE_LAND_GROUPING) /* NO GROUPING */\r
+        {\r
+            if (random(100)>=world_type) landtype_here=1;\r
+            else landtype_here=0;\r
+        }\r
+\r
+        if (landtype_here==0) topography[x]=\r
+            random (NUM_GRASS_TILES)+FIRST_GRASS_TILE;   /* GRASS */\r
+        else topography[x]=\r
+            random (NUM_DIRT_TILES)+FIRST_DIRT_TILE;     /* DIRT */\r
+    }\r
+\r
+    /* DO FIRST COLUMN */\r
+    for (y=1; y<WORLD_HEIGHT; y++)\r
+    {\r
+        landtype_up=isdirt(topography[WORLD_TILE(0,y-1)]);\r
+        landtype_here=landtype_up;\r
+               if (random(100)>=CHANCE_LAND_GROUPING) /* NO GROUPING */\r
+        {\r
+            if (random(100)>=world_type) landtype_here=1;\r
+            else landtype_here=0;\r
+        }\r
+\r
+        if (landtype_here==0) topography[WORLD_TILE(0,y)]=\r
+            random (NUM_GRASS_TILES)+FIRST_GRASS_TILE;   /* GRASS */\r
+        else topography[WORLD_TILE(0,y)]=\r
+            random (NUM_DIRT_TILES)+FIRST_DIRT_TILE;     /* DIRT */\r
+    }\r
+\r
+    /* DO SUBSEQUENT ROWS */\r
+    for (y=1; y<WORLD_HEIGHT; y++)\r
+    for (x=1; x<WORLD_WIDTH; x++)\r
+    {\r
+        landtype_left=isdirt(topography[WORLD_TILE(x-1,y)]);\r
+        landtype_up=isdirt(topography[WORLD_TILE(x,y-1)]);\r
+        landtype_here=landtype_left;\r
+\r
+        if (random(100)>=CHANCE_LAND_GROUPING) /* UNGROUP */\r
+        {\r
+            if (random(100)>=world_type) landtype_here=1;\r
+            else landtype_here=0;\r
+        }\r
+        else if (random(2)) landtype_here=landtype_up;\r
+\r
+        if (landtype_here==0) topography[WORLD_TILE(x,y)]=\r
+            random (NUM_GRASS_TILES)+FIRST_GRASS_TILE;   /* GRASS */\r
+        else topography[WORLD_TILE(x,y)]=\r
+            random (NUM_DIRT_TILES)+FIRST_DIRT_TILE;     /* DIRT */\r
+    }\r
+\r
+    if (edgemode) add_dirt_edges ();\r
+}\r
+\r
+/*\r
+ *\r
+ * Initializes background and foreground tile lists.\r
+ *\r
+ */\r
+void init_world(void)\r
+{\r
+    init_background();\r
+    init_foreground();\r
+    view_x=random(WORLD_WIDTH-VIEW_WIDTH);\r
+    view_y=random(WORLD_HEIGHT-VIEW_HEIGHT);\r
+}\r
diff --git a/16/roads/KEYS.H b/16/roads/KEYS.H
new file mode 100644 (file)
index 0000000..8958c78
--- /dev/null
@@ -0,0 +1,31 @@
+#define KEYS_H\r
+\r
+/* KEY DEFINITIONS */\r
+#define KEY_ENTER       13\r
+\r
+/* SCAN CODE DEFINITIONS */\r
+#define SCAN_A          30\r
+#define SCAN_B          48\r
+#define SCAN_C          46\r
+#define SCAN_E          18\r
+#define SCAN_F          33\r
+#define SCAN_G          34\r
+#define SCAN_K          37\r
+#define SCAN_Q          16\r
+#define SCAN_R          19\r
+#define SCAN_S          31\r
+#define SCAN_T          20\r
+#define SCAN_W          17\r
+\r
+#define SCAN_SPACE      57\r
+#define SCAN_ENTER      28\r
+#define SCAN_ESC        1\r
+\r
+#define SCAN_LEFTSHIFT  42\r
+#define SCAN_RIGHTSHIFT 54\r
+\r
+#define SCAN_UP         72\r
+#define SCAN_DOWN       80\r
+#define SCAN_LEFT       75\r
+#define SCAN_RIGHT      77\r
+\r
diff --git a/16/roads/MAKEFILE.MAK b/16/roads/MAKEFILE.MAK
new file mode 100644 (file)
index 0000000..a691457
--- /dev/null
@@ -0,0 +1,42 @@
+# Makefile\r
+.autodepend\r
+\r
+############################################## Main file name\r
+NAME = roads\r
+############################################## Object files\r
+OBJ1 = anim.obj bookends.obj initroad.obj fx.obj\r
+OBJ= $(OBJ1) roads.obj initw.obj tileio.obj\r
+############################################## Memory Model (ex: s for small)\r
+MODEL = s\r
+############################################## Supplementary dependencies\r
+SUP =\r
+############################################## Path to headers\r
+INCPATH = c:\borlandc\include\r
+############################################## Path to libraries\r
+LIBPATH = c:\borlandc\lib\r
+############################################## Libraries to search\r
+LIBS = c$(MODEL) fg$(MODEL)\r
+############################# emu math$(MODEL)\r
+############################################## Compilation Flags\r
+COMPFLAGS = -Z -G -O2 -c -H -m$(MODEL) -I$(INCPATH)\r
+############################################## Linking Flags\r
+LINKFLAGS = -x -L$(LIBPATH)\r
+\r
+\r
+#######\r
+# Implicit Definitions -- compilation\r
+#######\r
+.c.obj:\r
+        BCC $(COMPFLAGS) {$< }\r
+\r
+\r
+#######\r
+# Explicit Definitions -- linking\r
+#######\r
+$(NAME).exe: $(OBJ) $(SUP)\r
+        TLINK $(LINKFLAGS) @&&!\r
+c0$(MODEL) $(OBJ)\r
+$(NAME)\r
+\r
+$(LIBS)\r
+!\r
diff --git a/16/roads/READ.ME b/16/roads/READ.ME
new file mode 100644 (file)
index 0000000..7f5db3b
--- /dev/null
@@ -0,0 +1,54 @@
+4/1/94\r
+\r
+Don and I have decided to charge $50 for ROADS, plus $150 for the source\r
+code.\r
+\r
+4/2/94\r
+\r
+April Fools.\r
+\r
+ROADS is an attempt to play around with tiles and tiling methods.\r
+We were working on ROADS before Diana Gruber's PC Techniques\r
+articles on tiling came out, and I thought it would be nice to share\r
+some code showing a different mechanism for tiling.  It was written\r
+using Borland C++ 3.1 (by choice) and Fastgraph from Ted Gruber\r
+Software.\r
+\r
+ROADS uses 16x16 tiles that were drawn in Autodesk Animator.  They\r
+are stored on a hidden page in Mode-X and screen-to-screen copied as\r
+needed.  ROADS implements page flipping, so the frame rate will max\r
+out at 70fps (although I only get 29fps on my 386/40).  The screen\r
+scroll rate is 16 pixels, although 4 pixel scrolling could be added\r
+fairly easily by doing partial-tile copies.  The scrolling is pretty\r
+fast because, like in Diana Gruber's article, we copy the still\r
+valid portion of the screen to the new page, then update the new\r
+tiles.\r
+\r
+Tiles can animate (I'm pretty proud of my fire pit animation), even\r
+while scrolling, and fading.  The fading algorithm is based on a\r
+previous upload to the Dusk Devil BBS -- I took out the floating\r
+point calculations and made some modifications.  There's also a\r
+walking frog with red sneakers.  To discover what keys you can press\r
+when the program is running, type ROADS /?.  "Frog watch mode"\r
+doesn't work -- I wrote some bad code, and decided it wasn't worth\r
+fixing.  So much for the hacker ethic.\r
+\r
+The program uses Fastgraph's keyboard handler, so you can press, for\r
+example, down and right arrows together, and the view will scroll\r
+down right.\r
+\r
+Feel free to take any of the code you like for your own use.  I'm\r
+providing it because ... uh ... because I'm altruistic?  Nah.  The\r
+program doesn't really DO anything, just tests out some tiling\r
+algorithms.  Don flipped when I said I wanted to distribute the\r
+code, but when I reminded him that ROADS was an exercise in\r
+futility, he agreed we should share the pain.\r
+\r
+If you have any questions, comments, or conversation, I'd be pleased\r
+to hear from you.  Have fun!\r
+\r
+Dust Devil BBS (Home of Fastgraph):  Eric Lund\r
+Delphi:  ELUND or elund@delphi.com\r
+GEnie:  e.lund1 though I think I going to cancel it\r
+Compuserve:  74041,1147 or 74041.1147@compuserve.com\r
+\r
diff --git a/16/roads/ROADS.C b/16/roads/ROADS.C
new file mode 100644 (file)
index 0000000..2d153c8
--- /dev/null
@@ -0,0 +1,330 @@
+#define ROADS_C\r
+\r
+#include <fastgraf.h>\r
+#include <stdlib.h>\r
+#include <stdio.h>\r
+#include <time.h> /* FOR RANDOM */\r
+#include "roads.h"\r
+#include "tiles.h"    /* DUE TO R AND C CHEATS        */\r
+#include "fx.h"       /* FOR FADING STUFF             */\r
+#include "version.h"  /* INFO ON THIS VERSION         */\r
+#include "keys.h"     /* KEY AND SCANCODE DEFINITIONS */\r
+\r
+extern int far *topography;      /* BACKGROUND TILE LIST (ARRAY) */\r
+extern int far *terrain;      /* FOREGROUND TILE LIST (ARRAY) */\r
+extern int view_x, view_y;     /* VIEW AREA (UPPER LEFT CORNER) */\r
+extern int viewpage;           /* CURRENTLY VIEWED PAGE */\r
+extern int world_type;         /* TENDENCY TO GRASS */\r
+\r
+extern int edgemode;           /* BLOCKY GRASS/DIRT OR EDGED? */\r
+extern int animatemode;\r
+extern int frogmode;\r
+extern int frogwatchmode;\r
+\r
+int keyboardmode=0;\r
+\r
+/* PROTOTYPES FOR INTERNAL FUNCTIONS */\r
+void time_test (void);\r
+void cheat (int type);\r
+void toggle_mode (int type);\r
+void make_world (int type);\r
+void view_tile_page (void);\r
+void move_view (void);\r
+int keycheck (void);\r
+void init_all (void);\r
+void gogofrog (void);\r
+\r
+#pragma argsused\r
+void main (int argc, char *argv[])\r
+{\r
+    char quitting_time=0;       /* QUIT PROGRAM LOOP   */\r
+\r
+    printf (HEADER);\r
+\r
+    if (argc>1)\r
+    {\r
+        printf (KEY_HELP);\r
+        exit (2);\r
+    }\r
+\r
+    printf ("Loading ... [Escape quits] ... [Type ROADS /? for more keys!]\n");\r
+    init_all(); /* INITIALIZE ALL SYSTEMS */\r
+\r
+    while (!quitting_time) /* LOOP FOREVER */\r
+    {\r
+        quitting_time=keycheck(); /* CHECK FOR REGULAR KEYS */\r
+        if (animatemode) animate(); /* PERFORM ALL ANIMATIONS */\r
+    }\r
+\r
+    program_shutdown("Thank you for running ROADS!", 0);\r
+}\r
+\r
+#define TIMETEST_LENGTH 10 /* TIME TEST LENGTH IN SECONDS */\r
+\r
+/*\r
+ *\r
+ * Performs time testing to try and guess a FPS.\r
+ *\r
+ */\r
+void time_test (void)\r
+{\r
+    int x, dir;\r
+    long end_time;\r
+    int frames_shown[2];\r
+\r
+    for (x=0; x<2; x++) /* TEST TWICE, ONCE WITH ANIMATION */\r
+    {\r
+        while (redraw(SCROLL_UL)); /* SCROLL UPPER LEFT TO START */\r
+        fg_music ("L64FAC.AE.B$");\r
+        frames_shown[x]=0; dir=0;\r
+        end_time=TIMETEST_LENGTH*182/10;\r
+        end_time+=fg_getclock();\r
+\r
+        while (fg_getclock()<end_time)\r
+        {\r
+            frames_shown[x]++;\r
+\r
+            switch (dir)\r
+            {\r
+                case 0: if (!redraw (SCROLL_DR   )) dir++; break;\r
+                case 1: if (!redraw (SCROLL_UP   )) dir++; break;\r
+                case 2: if (!redraw (SCROLL_DL   )) dir++; break;\r
+                case 3: if (!redraw (SCROLL_RIGHT)) dir++; break;\r
+                case 4: if (!redraw (SCROLL_UL   )) dir++; break;\r
+                case 5: if (!redraw (SCROLL_DOWN )) dir++; break;\r
+                case 6: if (!redraw (SCROLL_UR   )) dir++; break;\r
+                case 7: if (!redraw (SCROLL_LEFT )) dir=0; break;\r
+            }\r
+\r
+            if (x==0) animate(); /* ANIMATION ON FIRST TEST ONLY */\r
+        }\r
+    }\r
+\r
+    program_shutdown ("",-1); /* DON'T EXIT YET */\r
+    printf ("%d Frames in %i seconds (%i FPS) with animation\n",\r
+        frames_shown[0], TIMETEST_LENGTH, frames_shown[0]/TIMETEST_LENGTH);\r
+    printf ("%d Frames in %i seconds (%i FPS) without animation\n",\r
+        frames_shown[1], TIMETEST_LENGTH, frames_shown[1]/TIMETEST_LENGTH);\r
+    exit (0);\r
+}\r
+\r
+/*\r
+ *\r
+ * Turns on cheats (fills screen with anims for testing)\r
+ *\r
+ */\r
+void cheat (int type)\r
+{\r
+    register int x;\r
+\r
+    if (type==0)\r
+    {\r
+        fade_out_all();\r
+        for (x=0; x<WORLD_TILES_TOTAL; x++)\r
+            terrain[x]=ANM_FIRE;\r
+        redraw(REFRESH);\r
+        animatewhilefading=0;\r
+        fade_in_all();\r
+        animatewhilefading=1;\r
+    }\r
+\r
+    else if (type==1)\r
+    {\r
+        fade_out_all();\r
+        for (x=0; x<WORLD_TILES_TOTAL; x++)\r
+            if (!isroad(terrain[x])) terrain[x]=ANM_FIRE;\r
+        redraw(REFRESH);\r
+        animatewhilefading=0; /* DON'T ANIMATE IN FADES -- TOO MANY ANIMS! */\r
+        fade_in_all();\r
+        animatewhilefading=1;\r
+    }\r
+}\r
+\r
+/*\r
+ *\r
+ * Toggles modes on and off (animation, edging, etc.)\r
+ *\r
+ */\r
+void toggle_mode (int type)\r
+{\r
+    if (type==0)\r
+    {\r
+        animatemode=!animatemode;\r
+        fg_music ("L64G.AG.A$");\r
+    }\r
+\r
+    else if (type==1)\r
+    {\r
+        edgemode=!edgemode;\r
+        fg_music ("S1L20B..G..F..$");\r
+        if (edgemode)\r
+        {\r
+            add_dirt_edges();\r
+            redraw(REFRESH);\r
+        }\r
+    }\r
+    else if (type==2)\r
+    {\r
+        keyboardmode=!keyboardmode;\r
+        fg_music ("L40BABAGFG.$");\r
+    }\r
+    else if (type==3)\r
+    {\r
+        frogwatchmode=!frogwatchmode;\r
+        fg_music ("O1L30D.ED.A$");\r
+    }\r
+}\r
+\r
+/*\r
+ *\r
+ * Initializes the world, foreground and/or background.\r
+ *\r
+ */\r
+void make_world (int type)\r
+{\r
+    if (type==0)\r
+    {\r
+        fade_out_all();\r
+        init_world();\r
+        redraw(REFRESH);\r
+        fade_in_all();\r
+    }\r
+\r
+    else if (type==1)\r
+    {\r
+        init_background();\r
+        redraw(REFRESH);\r
+    }\r
+\r
+    else if (type==2)\r
+    {\r
+        init_foreground();\r
+        redraw(REFRESH);\r
+    }\r
+}\r
+\r
+/*\r
+ *\r
+ * Shows the contents of the tile page.  Hit any key to fade back.\r
+ *\r
+ */\r
+void view_tile_page (void)\r
+{\r
+    char key1, key2;\r
+\r
+    fade_out_all();\r
+    fg_setvpage (TILEPAGE);\r
+    fade_in_all();\r
+\r
+    fg_kbinit(0);\r
+\r
+    do {\r
+      fg_intkey (&key1, &key2);\r
+      animate();\r
+    } while (!(key1+key2)); /* DO LOOP WHILE KEYS ARE NOT HIT */\r
+\r
+    fg_kbinit(1);\r
+\r
+    fade_out_all();\r
+    fg_setvpage (viewpage);\r
+    fade_in_all();\r
+}\r
+\r
+/*\r
+ *\r
+ * Scans for arrow keys and scrolls the view area in reaction to them.\r
+ *\r
+ */\r
+void move_view (void)\r
+{\r
+    static char left=0, right=0, up=0, down=0; /* KEYBOARD VARS       */\r
+\r
+        /* CHECK FOR ARROWS BEING PRESSED */\r
+    if (fg_kbtest(SCAN_RIGHT))      right++; else right=0;\r
+    if (fg_kbtest(SCAN_LEFT))       left++;  else left=0;\r
+    if (fg_kbtest(SCAN_UP))         up++;    else up=0;\r
+    if (fg_kbtest(SCAN_DOWN))       down++;  else down=0;\r
+\r
+        /* MAKE SURE COUNTERS DON'T GO TOO HIGH */\r
+    if (right>100)  right=100;\r
+    if (left>100)   left=100;\r
+    if (up>100)     up=100;\r
+    if (down>100)   down=100;\r
+\r
+        /* IF "TAP" KEYBOARD MODE IS ON, DON'T MOVE UNTIL KEYS RELEASED */\r
+    if (keyboardmode && (right>1 || left>1 || up>1 || down>1)) return;\r
+\r
+        /* MOVE, CHECKING FOR DIAGONAL MOVEMENT FIRST */\r
+    if (up && right)        redraw (SCROLL_UR);\r
+    else if (down && left)  redraw (SCROLL_DL);\r
+    else if (up && left)    redraw (SCROLL_UL);\r
+    else if (down && right) redraw (SCROLL_DR);\r
+    else if (right)         redraw (SCROLL_RIGHT);\r
+    else if (left)          redraw (SCROLL_LEFT);\r
+    else if (up)            redraw (SCROLL_UP);\r
+    else if (down)          redraw (SCROLL_DOWN);\r
+}\r
+\r
+/*\r
+ *\r
+ * Initializes all systems and brings display up.\r
+ *\r
+ */\r
+void init_all (void)\r
+{\r
+    fg_kbinit(1);       /* LATCH LOW-LEVEL KEYBOARD HANDLER */\r
+    randomize();        /* ALLOW RANDOMIZATIONS             */\r
+\r
+    init_anim();        /* CALL BEFORE WORLD CREATION       */\r
+    init_data();        /* CALL BEFORE WORLD CREATION       */\r
+    init_world();       /* RANDOMIZE THE WORLD              */\r
+    init_video();       /* SET OUR VIDEO MODE ETC.          */\r
+\r
+    fade_init();        /* ALLOW FADING                     */\r
+    fade_blackout();    /* SET ALL COLORS TO BLACK          */\r
+    redraw(REFRESH);    /* DRAW THE SCREEN (UNSEEN)         */\r
+    fade_in_all();      /* FADE IN SCREEN                   */\r
+}\r
+\r
+/*\r
+ *\r
+ * Keycheck checks all keys and reacts upon them.\r
+ * Returns 1 if a key has indicated the user has requested to quit.\r
+ *\r
+ */\r
+int keycheck (void)\r
+{\r
+    if (fg_kbtest(SCAN_T)) time_test();\r
+    if (fg_kbtest(SCAN_C)) cheat (0);\r
+    if (fg_kbtest(SCAN_R)) cheat (1);\r
+    if (fg_kbtest(SCAN_A)) toggle_mode(0);\r
+    if (fg_kbtest(SCAN_E)) toggle_mode(1);\r
+    if (fg_kbtest(SCAN_K)) toggle_mode(2);\r
+    if (fg_kbtest(SCAN_W)) toggle_mode(3);\r
+    if (fg_kbtest(SCAN_F))\r
+    {\r
+        fg_music ("L50O4BAFDEF.$");\r
+        switch (frogmode)\r
+        {\r
+            case 1: frogmode=2; break;\r
+            case 3: frogmode=0; break;\r
+        }\r
+    }\r
+    if (fg_kbtest(SCAN_G)) /* RERANDOMIZE GRASS/DIRT TENDENCY */\r
+    {\r
+        world_type=random(100);\r
+        fg_music ("S1L20C..B..A..$");\r
+    }\r
+    if (fg_kbtest(SCAN_SPACE))  make_world (0);\r
+    if (fg_kbtest(SCAN_B))      make_world (1);\r
+    if (fg_kbtest(SCAN_ENTER))  make_world (2);\r
+    if (fg_kbtest(SCAN_S))      view_tile_page();\r
+\r
+    move_view(); /* RESPOND TO ARROW KEYS MOVING VIEW */\r
+\r
+    if (fg_kbtest(SCAN_ESC) || fg_kbtest(SCAN_Q)) /* ESCAPE TO QUIT */\r
+        return 1;\r
+\r
+    return 0;\r
+}\r
+\r
diff --git a/16/roads/ROADS.EXE b/16/roads/ROADS.EXE
new file mode 100644 (file)
index 0000000..b8aa016
Binary files /dev/null and b/16/roads/ROADS.EXE differ
diff --git a/16/roads/ROADS.GIF b/16/roads/ROADS.GIF
new file mode 100644 (file)
index 0000000..496b612
Binary files /dev/null and b/16/roads/ROADS.GIF differ
diff --git a/16/roads/ROADS.H b/16/roads/ROADS.H
new file mode 100644 (file)
index 0000000..d4e10a9
--- /dev/null
@@ -0,0 +1,143 @@
+#define ROADS_H\r
+\r
+/* GENERIC TILING DEFINITIONS */\r
+\r
+#define WORLD_WIDTH       105    /* IN TILES, WIDTH OF "PLAYFIELD"  */\r
+#define WORLD_HEIGHT      100    /* IN TILES, HEIGHT OF "PLAYFIELD" */\r
+#define WORLD_TILES_TOTAL 10500  /* (WORLD_WIDTH*WORLD_HEIGHT)     */\r
+\r
+#define TILE_WIDTH       16  /* IN PIXELS, WIDTH OF TILE    */\r
+#define TILE_HEIGHT      16  /* IN PIXELS, HEIGHT OF TILE   */\r
+#define VIEW_WIDTH       20  /* (SCREEN_WIDTH/TILE_WIDTH)   */\r
+#define VIEW_HEIGHT      15  /* (SCREEN_HEIGHT/TILE_HEIGHT) */\r
+#define VIEW_TILES_TOTAL 300 /* (VIEW_WIDTH*VIEW_HEIGHT)    */\r
+#define MIXING_TILE      299 /* (VIEW_TILES_TOTAL-1)        */\r
+#define EMPTY_TILE -1   /* STANDARD INDEX FOR A SEE-THROUGH TILE */\r
+#define ERROR_TILE -2   /* STANDARD INDEX FOR ERRORS! */\r
+\r
+/* PROTOTYPES */\r
+\r
+void create_roads (void);\r
+int roadexit (int road, int direction);\r
+int makeintersection (int road, int ramp);\r
+void init_foreground (void);\r
+void init_background (void);\r
+void add_dirt_edges (void);\r
+void init_world(void);\r
+int redraw (int draw_type);\r
+void init_video (void);\r
+void init_data (void);\r
+void program_shutdown (char *msg, int errcode);\r
+void place_tile_block (int x1, int y1, int x2, int y2);\r
+void init_anim (void);\r
+void animate (void);\r
+void update_tile (int tile);\r
+void gogofrog (void);\r
+\r
+/* VIDEO MODE DEFINITIONS */\r
+#define VMODE  22\r
+#define VPAGES 3\r
+#define SCREEN_WIDTH    320\r
+#define SCREEN_HEIGHT   240\r
+#define IMAGES "roads.gif" /* IMAGE CONTAINING TILES */\r
+\r
+/* VIDEO PAGE DEFINITIONS */\r
+#define VIEWPAGE1 0\r
+#define VIEWPAGE2 1\r
+#define TILEPAGE  2\r
+\r
+/* STANDARD DEFINITIONS */\r
+#define FALSE 0\r
+#define TRUE 1\r
+\r
+/* CHECKS IF A TILE IS A ROAD OR NOT */\r
+#define isroad(r) (r>=FIRST_ROAD_TILE && r<=LAST_ROAD_TILE)\r
+\r
+#define REFRESH         0\r
+#define SCROLL_UP       1\r
+#define SCROLL_DOWN     2\r
+#define SCROLL_LEFT     3\r
+#define SCROLL_RIGHT    4\r
+#define SCROLL_UR       5\r
+#define SCROLL_DR       6\r
+#define SCROLL_DL       7\r
+#define SCROLL_UL       8\r
+#define NONFLIP_SCROLL_UP       11\r
+#define NONFLIP_SCROLL_DOWN     12\r
+#define NONFLIP_SCROLL_LEFT     13\r
+#define NONFLIP_SCROLL_RIGHT    14\r
+#define NONFLIP_REFRESH         15\r
+\r
+#define MAX_FAILS 50 /* MAXIMUM NUMBER OF ATTEMPTS TO PLACE TILES */\r
+\r
+/*\r
+ *\r
+ * MACROS TO CONVERT TILES FROM "ROW/COLUMN" FORMAT TO "INDEX" FORMAT\r
+ *\r
+ */\r
+\r
+        /* WORLD SPACE CONVERSIONS */\r
+#define WORLD_TILE(x,y) ((x)+(y)*WORLD_WIDTH) /* CONVERTS DUAL X,Y TO SINGLE */\r
+#define WORLD_TILE_X(z) ((z)%WORLD_WIDTH)     /* CONVERTS SINGLE TO DUAL X   */\r
+#define WORLD_TILE_Y(z) ((z)/WORLD_WIDTH)     /* CONVERTS SINGLE TO DUAL Y   */\r
+\r
+        /* VIEW SPACE CONVERSIONS */\r
+#define VIEW_TILE(x,y)  ((x)+(y)*VIEW_WIDTH)  /* CONVERTS DUAL X,Y TO SINGLE */\r
+#define VIEW_TILE_X(z)  ((z)%VIEW_WIDTH)      /* CONVERTS SINGLE TO DUAL X   */\r
+#define VIEW_TILE_Y(z)  ((z)/VIEW_WIDTH)      /* CONVERTS SINGLE TO DUAL Y   */\r
+\r
+/* RETURNS 1 IF A SINGLE WORLD TILE INDEX IS WITHIN THE VIEWING SCREEN */\r
+#define is_viewable(x)  (WORLD_TILE_X(x)>=view_x &&          \\r
+                        WORLD_TILE_X(x)<view_x+VIEW_WIDTH && \\r
+                        WORLD_TILE_Y(x)>=view_y &&           \\r
+                        WORLD_TILE_Y(x)<view_y+VIEW_HEIGHT)  \\r
+\r
+/* CONVERTS A WORLD INDEX INTO A VIEW INDEX -- USE IS_VIEWABLE TO MAKE */\r
+/* SURE THE WORLD INDEX IS WITHIN THE VIEWING AREA!                    */\r
+#define WORLD_TO_VIEW(x) VIEW_TILE(WORLD_TILE_X((x))-view_x,WORLD_TILE_Y((x))-view_y)\r
+\r
+#define DONS_VIEW_TO_WORLD(x) (WORLD_TILE (view_x, view_y) + (VIEW_TILE_Y (x) * WORLD_WIDTH) + VIEW_TILE_X (x)  /* CONVERT VIEW SINGLE TO WORLD SINGLE */\r
+#define VIEW_TO_WORLD(x) (WORLD_TILE (VIEW_TILE_X (x) + view_x, VIEW_TILE_Y (x) + view_y)) /* CONVERT VIEW SINGLE TO WORLD SINGLE */\r
+\r
+/*\r
+ *\r
+ * MACROS TO EASE COPYING OF TILES BETWEEN AND AMONG PAGES\r
+ *\r
+ */\r
+\r
+   /* INTENRAL MACRO USED IN COPY TILE MACROS -- USER, DO NOT CALL! */\r
+#define COPY_TILE_CORE(from,to,spage,dpage)        \\r
+    ((from)%VIEW_WIDTH)*TILE_WIDTH,         \\r
+    (((from)%VIEW_WIDTH)+1)*TILE_WIDTH-1,   \\r
+    ((from)/VIEW_WIDTH)*TILE_HEIGHT,        \\r
+    (((from)/VIEW_WIDTH)+1)*TILE_WIDTH-1,   \\r
+    ((to)%VIEW_WIDTH)*TILE_WIDTH,           \\r
+    (((to)/VIEW_WIDTH)+1)*TILE_HEIGHT-1,    \\r
+    spage, dpage\r
+\r
+    /* TRANSPARENT TILE COPY FROM HIDDEN PAGE TO ACTIVE PAGE */\r
+#define copy_tile_tr(from_tile,to_tile) \\r
+    fg_tcxfer (COPY_TILE_CORE(from_tile,to_tile,TILEPAGE,VIEWPAGE))\r
+\r
+    /* OPAQUE TILE COPY FROM HIDDEN PAGE TO ACTIVE PAGE */\r
+#define copy_tile_op(from_tile,to_tile) \\r
+    fg_transfer (COPY_TILE_CORE(from_tile,to_tile,TILEPAGE,VIEWPAGE))\r
+\r
+    /* TRANSPARENT TILE COPY */\r
+#define pagecopy_tile_tr(from_tile,to_tile,sourcepage,destpage) \\r
+    fg_tcxfer (COPY_TILE_CORE(from_tile,to_tile,sourcepage,destpage))\r
+\r
+    /* OPAQUE TILE COPY FROM */\r
+#define pagecopy_tile_op(from_tile,to_tile,sourcepage,destpage) \\r
+    fg_transfer (COPY_TILE_CORE(from_tile,to_tile,sourcepage,destpage))\r
+\r
+struct ANIMATION\r
+{\r
+    int anm;        /* NUMBER USED TO ENCODE ANIMATION IN WORLD */\r
+    int total;      /* TOTAL NUMBER OF TILES USED IN ANIMATION  */\r
+    int first;      /* FIRST TILE NO. IN ANIMATION              */\r
+    int current;    /* TILE CURRENTLY BEING SHOWN IN ANIMATION  */\r
+    int delay;      /* DELAY BETWEEN TILES IN CLOCK TICKS       */\r
+    long next;      /* TIME OF NEXT UPDATE                      */\r
+};\r
+\r
diff --git a/16/roads/TILEIO.C b/16/roads/TILEIO.C
new file mode 100644 (file)
index 0000000..f04956b
--- /dev/null
@@ -0,0 +1,178 @@
+#define TILEIO_C\r
+\r
+#include <fastgraf.h>\r
+#include "roads.h"\r
+#include "tiles.h" /* NEEDS ACCESS BECAUSE OF ANIMS */\r
+\r
+extern int far *topography;      /* BACKGROUND TILE LIST (ARRAY) */\r
+extern int far *terrain;      /* FOREGROUND TILE LIST (ARRAY) */\r
+extern struct ANIMATION *anim_list[ANIM_LIST_TOTAL];\r
+\r
+int view_x=0, view_y=0;        /* VIEW AREA (UPPER LEFT CORNER) */\r
+int viewpage=0;                /* CURRENTLY VIEWED PAGE */\r
+\r
+/*\r
+ *\r
+ * Redraws the screen view area.  Returns 1 on successful scroll.\r
+ *\r
+ */\r
+int redraw (int draw_type)\r
+{\r
+    int new_draw_type;\r
+    int pageflip=1;\r
+\r
+    switch (draw_type)\r
+    {\r
+        case NONFLIP_SCROLL_RIGHT:\r
+            new_draw_type=SCROLL_RIGHT; pageflip=0; break;\r
+        case NONFLIP_SCROLL_LEFT:\r
+            new_draw_type=SCROLL_LEFT; pageflip=0; break;\r
+        case NONFLIP_SCROLL_UP:\r
+            new_draw_type=SCROLL_UP; pageflip=0; break;\r
+        case NONFLIP_SCROLL_DOWN:\r
+            new_draw_type=SCROLL_DOWN; pageflip=0; break;\r
+        case NONFLIP_REFRESH:\r
+            new_draw_type=REFRESH; pageflip=0; break;\r
+        default:\r
+            new_draw_type=draw_type; break;\r
+    }\r
+\r
+    switch (new_draw_type)\r
+       {\r
+        case REFRESH: /* Full Refresh */\r
+        place_tile_block (0, 0, VIEW_WIDTH-1, VIEW_HEIGHT-1);\r
+        break;\r
+\r
+        case SCROLL_UP:\r
+        if (view_y<=0) return 0; /* DON'T ALLOW TO SCROLL BEYOND WORLD */\r
+        view_y--;\r
+        fg_transfer (0, SCREEN_WIDTH-1, 0, SCREEN_HEIGHT-TILE_HEIGHT-1, 0, SCREEN_HEIGHT-1, viewpage, !viewpage);\r
+        place_tile_block (0, 0, VIEW_WIDTH-1, 0);\r
+        break;\r
+\r
+        case SCROLL_DOWN:\r
+        if (view_y>=WORLD_HEIGHT-VIEW_HEIGHT) return 0; /* HEY! */\r
+        view_y++;\r
+        fg_transfer (0, SCREEN_WIDTH-1, TILE_HEIGHT, SCREEN_HEIGHT-1, 0, SCREEN_HEIGHT-TILE_HEIGHT-1, viewpage, !viewpage);\r
+        place_tile_block (0, VIEW_HEIGHT-1, VIEW_WIDTH-1, VIEW_HEIGHT-1);\r
+        break;\r
+\r
+        case SCROLL_LEFT:\r
+        if (view_x<=0) return 0; /* DON'T ALLOW TO SCROLL BEYOND WORLD */\r
+        view_x--;\r
+        fg_transfer (0, SCREEN_WIDTH-TILE_WIDTH-1, 0, SCREEN_HEIGHT-1, TILE_WIDTH, SCREEN_HEIGHT-1, viewpage, !viewpage);\r
+        place_tile_block (0, 0, 0, VIEW_HEIGHT-1);\r
+        break;\r
+\r
+        case SCROLL_RIGHT:\r
+        if (view_x>=WORLD_WIDTH-VIEW_WIDTH) return 0; /* HEY! */\r
+        view_x++;\r
+        fg_transfer (TILE_WIDTH, SCREEN_WIDTH-1, 0, SCREEN_HEIGHT-1, 0, SCREEN_HEIGHT-1, viewpage, !viewpage);\r
+        place_tile_block (VIEW_WIDTH-1, 0, VIEW_WIDTH-1, VIEW_HEIGHT-1);\r
+        break;\r
+\r
+        case SCROLL_UR:\r
+        if (view_x>=WORLD_WIDTH-VIEW_WIDTH) return redraw (SCROLL_UP);\r
+        if (view_y<=0) return redraw(SCROLL_RIGHT);\r
+        view_y--;  view_x++;\r
+        fg_transfer (TILE_WIDTH, SCREEN_WIDTH-1, 0, SCREEN_HEIGHT-TILE_HEIGHT-1, 0, SCREEN_HEIGHT-1, viewpage, !viewpage);\r
+        place_tile_block (0, 0, VIEW_WIDTH-1, 0);\r
+        place_tile_block (VIEW_WIDTH-1, 0, VIEW_WIDTH-1, VIEW_HEIGHT-1);\r
+        break;\r
+\r
+        case SCROLL_DR:\r
+        if (view_x>=WORLD_WIDTH-VIEW_WIDTH) return redraw(SCROLL_DOWN);\r
+        if (view_y>=WORLD_HEIGHT-VIEW_HEIGHT) return redraw(SCROLL_RIGHT);\r
+        view_y++;  view_x++;\r
+        fg_transfer (TILE_WIDTH, SCREEN_WIDTH-1, TILE_HEIGHT, SCREEN_HEIGHT-1, 0, SCREEN_HEIGHT-TILE_HEIGHT-1, viewpage, !viewpage);\r
+        place_tile_block (0, VIEW_HEIGHT-1, VIEW_WIDTH-1, VIEW_HEIGHT-1);\r
+        place_tile_block (VIEW_WIDTH-1, 0, VIEW_WIDTH-1, VIEW_HEIGHT-1);\r
+        break;\r
+\r
+        case SCROLL_DL:\r
+        if (view_x<=0) return redraw (SCROLL_DOWN);\r
+        if (view_y>=WORLD_HEIGHT-VIEW_HEIGHT) return redraw(SCROLL_LEFT);\r
+        view_y++;  view_x--;\r
+        fg_transfer (0, SCREEN_WIDTH-TILE_WIDTH-1, TILE_HEIGHT, SCREEN_HEIGHT-1, TILE_WIDTH, SCREEN_HEIGHT-TILE_HEIGHT-1, viewpage, !viewpage);\r
+        place_tile_block (0, VIEW_HEIGHT-1, VIEW_WIDTH-1, VIEW_HEIGHT-1);\r
+        place_tile_block (0, 0, 0, VIEW_HEIGHT-1);\r
+        break;\r
+\r
+        case SCROLL_UL:\r
+        if (view_x<=0) return redraw (SCROLL_UP);\r
+        if (view_y<=0) return redraw (SCROLL_LEFT);\r
+        view_y--;  view_x--;\r
+        fg_transfer (0, SCREEN_WIDTH-TILE_WIDTH-1, 0, SCREEN_HEIGHT-TILE_HEIGHT-1, TILE_WIDTH, SCREEN_HEIGHT-1, viewpage, !viewpage);\r
+        place_tile_block (0, 0, VIEW_WIDTH-1, 0);\r
+        place_tile_block (0, 0, 0, VIEW_HEIGHT-1);\r
+        break;\r
+    }\r
+\r
+    if (pageflip)\r
+    {\r
+        viewpage=!viewpage; /* SWAP PAGES */\r
+        fg_setvpage (viewpage);\r
+    }\r
+\r
+    return 1; /* SUCCESSFUL SCROLL */\r
+}\r
+\r
+/*\r
+ *\r
+ * Redraws a rectangular region of the active (!shown) page with tiles.\r
+ *\r
+ */\r
+\r
+void place_tile_block (int x1, int y1, int x2, int y2)\r
+{\r
+    register int x, y;\r
+    int tile, a;\r
+\r
+    for (y=view_y+y1; y<=view_y+y2; y++)\r
+    for (x=view_x+x1; x<=view_x+x2; x++)\r
+    {\r
+        tile=terrain[WORLD_TILE(x,y)];\r
+\r
+            /* DON'T PLACE FOREGROUND IF EMPTY */\r
+        if (tile==EMPTY_TILE)\r
+            pagecopy_tile_op (topography[WORLD_TILE(x,y)],VIEW_TILE(x-view_x,y-view_y), TILEPAGE, !viewpage);\r
+\r
+        else\r
+        {\r
+            if (is_anim(tile))\r
+            {\r
+                a=0;\r
+                while (anim_list[a]->anm!=tile) a++;\r
+                tile=anim_list[a]->current;\r
+            }\r
+\r
+            pagecopy_tile_op (topography[WORLD_TILE(x,y)], VIEW_TILE(x-view_x,y-view_y), TILEPAGE, !viewpage);\r
+            pagecopy_tile_tr (tile, VIEW_TILE(x-view_x,y-view_y), TILEPAGE, !viewpage);\r
+        }\r
+    }\r
+}\r
+\r
+/*\r
+ *\r
+ * Updates on the visual page a single world tile.\r
+ *\r
+ */\r
+void update_tile (int tile)\r
+{\r
+    register int viewtile;\r
+\r
+    if (!is_viewable (tile)) return; /* DO NOT UPDATE THE UNSEEN */\r
+\r
+    viewtile=VIEW_TILE(WORLD_TILE_X(tile)-view_x,WORLD_TILE_Y(tile)-view_y);\r
+\r
+    if (terrain[tile]==EMPTY_TILE)\r
+        pagecopy_tile_op (topography[tile], viewtile, TILEPAGE, viewpage);\r
+\r
+    else\r
+    {\r
+        pagecopy_tile_op (topography[tile], MIXING_TILE, TILEPAGE, TILEPAGE);\r
+        pagecopy_tile_tr (terrain[tile], MIXING_TILE, TILEPAGE, TILEPAGE);\r
+        pagecopy_tile_op (MIXING_TILE, viewtile, TILEPAGE, viewpage);\r
+    }\r
+}\r
+\r
diff --git a/16/roads/TILES.H b/16/roads/TILES.H
new file mode 100644 (file)
index 0000000..f8e003c
--- /dev/null
@@ -0,0 +1,123 @@
+#define TILE_H\r
+\r
+/*\r
+ *\r
+ * Regular non-animated tiles.\r
+ *\r
+ */\r
+#define OBJ_SIGN        20\r
+#define OBJ_ROCK1       56\r
+#define OBJ_ROCK2       57\r
+#define OBJ_ROCK3       58\r
+#define OBJ_FIRST_GEM   23\r
+#define OBJ_TOTAL_GEM   4\r
+#define OBJ_BUSHES      27\r
+\r
+/*\r
+ *\r
+ * Animation designations.  Edit actual tile numbers in anim.c.\r
+ *\r
+ */\r
+#define ANIM_LIST_TOTAL 5 /* INCREASE WHEN ADDING struct ANIMATIONs */\r
+#define ANM_START       500\r
+\r
+#define ANM_FIRE        500\r
+#define ANM_WATER1      501\r
+#define ANM_WATER2      502\r
+#define ANM_URANIUM     503\r
+#define ANM_FROG        504\r
+\r
+/*\r
+ *\r
+ * Frog with sneakers\r
+ *\r
+ */\r
+#define FROG_FACE_RIGHT 80\r
+#define FROG_WALK_RIGHT 81\r
+#define FROG_FACE_DOWN  85\r
+#define FROG_WALK_DOWN  86\r
+#define FROG_FACE_LEFT  90\r
+#define FROG_WALK_LEFT  91\r
+#define FROG_FACE_UP    95\r
+#define FROG_WALK_UP    96\r
+#define FROG_NUM_WALKS  4\r
+\r
+/*\r
+ *\r
+ * Dirt-into-grass edges.\r
+ *\r
+ */\r
+#define DIRTEDGE_U    40\r
+#define DIRTEDGE_R    41\r
+#define DIRTEDGE_D    42\r
+#define DIRTEDGE_L    43\r
+#define DIRTEDGE_UR   44\r
+#define DIRTEDGE_UD   45\r
+#define DIRTEDGE_UL   46\r
+#define DIRTEDGE_RD   47\r
+#define DIRTEDGE_RL   48\r
+#define DIRTEDGE_DL   49\r
+#define DIRTEDGE_URD  50\r
+#define DIRTEDGE_URL  51\r
+#define DIRTEDGE_RDL  52\r
+#define DIRTEDGE_URDL 53\r
+#define DIRTEDGE_UDL  54\r
+\r
+/*\r
+ *\r
+ * Road tiles\r
+ *\r
+ */\r
+#define ROAD_V   0  /* ³ */\r
+#define ROAD_H   1  /* Ä */\r
+#define ROAD_UR  2  /* Ú */\r
+#define ROAD_UL  3  /* ¿ */\r
+#define ROAD_DR  4  /* À */\r
+#define ROAD_DL  5  /* Ù */\r
+#define ROAD_X   6  /* Å */\r
+#define ROAD_TL  7  /* ´ */\r
+#define ROAD_TR  8  /* Ã */\r
+#define ROAD_TU  9  /* Á */\r
+#define ROAD_TD 10  /* Â */\r
+\r
+/* AIDS TO ROADS */\r
+#define FIRST_ROAD_TILE 0\r
+#define NUM_ROAD_TILES 11\r
+#define LAST_ROAD_TILE 10\r
+\r
+/*\r
+ *\r
+ * More tile definitions, mainly for grouping tiles together.\r
+ *\r
+ */\r
+#define NUM_GRASS_TILES 7   /* NUMBER OF "GRASS" TILES */\r
+#define FIRST_GRASS_TILE 11 /* INDEX OF FIRST GRASS TILE */\r
+\r
+#define NUM_DIRT_TILES 2\r
+#define FIRST_DIRT_TILE 18\r
+\r
+#define NUM_LAND_TILES 9 /* INCLUDES GRASS AND DIRT */\r
+#define FIRST_LAND_TILE 11\r
+\r
+#define CHANCE_LAND_GROUPING 75 /* PERCENT CHANCE GROUPING WILL OCCUR */\r
+\r
+#define MIN_TERRAIN (WORLD_TILES_TOTAL/100)\r
+#define MAX_TERRAIN (WORLD_TILES_TOTAL/2)\r
+\r
+/*\r
+ *\r
+ * General animation defines -- others are at top of this file.\r
+ *\r
+ */\r
+#define ANM_END         (ANM_START+ANIM_LIST_TOTAL)\r
+#define is_anim(x) ((x)>=ANM_START && (x)<ANM_END)\r
+\r
+/*\r
+ *\r
+ * Macros for testing a tile index' contents.\r
+ *\r
+ */\r
+#define isdirt(d) ((d)>=FIRST_DIRT_TILE && (d)<FIRST_DIRT_TILE+NUM_DIRT_TILES)\r
+#define isgrass(g) ((g)>=FIRST_GRASS_TILE && (g)<FIRST_GRASS_TILE+NUM_GRASS_TILES)\r
+#define island(l) (isdirt(l) || island(l))\r
+\r
diff --git a/16/roads/VERSION.H b/16/roads/VERSION.H
new file mode 100644 (file)
index 0000000..8c80a2d
--- /dev/null
@@ -0,0 +1,22 @@
+#define VERSION_H\r
+\r
+#define KEY_HELP                                               \\r
+"T           Determine FPS\n"                                  \\r
+"C           Cheat -- Fill screen with animations\n"           \\r
+"R           Cheat, but save roads\n"                          \\r
+"E           Toggle dirt/grass edging\n"                       \\r
+"A           Toggle off/on animations\n"                       \\r
+"SPACE       Randomize foreground and background\n"            \\r
+"B           Randomize background\n"                           \\r
+"ENTER       Randomize foreground\n"                           \\r
+"G           Randomize amount of grassiness for next world\n"  \\r
+"ESC         QUIT this damn program I'm sick of it!\n"         \\r
+"S           Check out the SPRITE page\n"                      \\r
+"K           Toggle Keyboard Delay ON/OFF\n"                   \\r
+"F           Bring on the frog!\n"                             \\r
+"W           Frog Watch mode\n"                                \\r
+"ARROW KEYS  Move around in the world\n"\r
+\r
+#define HEADER \\r
+"ROADS version 15.0  -- Sloppyright (S) Eric W. Lund and Donald D. Dienst\n"\r
+\r
diff --git a/16/starport2/FCINFO12.TXT b/16/starport2/FCINFO12.TXT
new file mode 100644 (file)
index 0000000..7530450
--- /dev/null
@@ -0,0 +1,1244 @@
+ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿\r
+³                                                                             ³\r
+³                <<<  THE FUTURE CREW INFORMATION PACKAGE  >>>                ³\r
+³                                                                             ³\r
+³                                Version 1.2                                  ³\r
+³                                                                             ³\r
+³                                02-DEC-1993                                  ³\r
+³                                                                             ³\r
+³                                                                             ³\r
+³            This file contains general information about the Future          ³\r
+³            Crew and our demos. It also includes frequently asked            ³\r
+³            questions we often receive by mail and instructions on           ³\r
+³            how to contact us best.                                          ³\r
+³                                                                             ³\r
+³            We will update this file as things change, and if the            ³\r
+³            above date is rather old, you can get the most recent            ³\r
+³            version of this file either by E-Mail from Internet or           ³\r
+³            from our distribution sites.                                     ³\r
+³                                                                             ³\r
+³                                                                             ³\r
+³                                                                             ³\r
+³                                                                             ³\r
+ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ\r
+\r
+\r
+ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿\r
+³                                 CONTENTS                                    ³\r
+ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ\r
+\r
+         1: Opening words\r
+         2: Demos for Commercial Purposes\r
+         3: The Distribution and Use of Our Demos\r
+         4: The Current Memberstatus\r
+         5: International Demo Competitions\r
+         6: Official Assembly'93 Competition Results\r
+         7: Quick Information on The Party 3\r
+         8: How to Contact Future Crew\r
+         9: Frequently Asked Questions\r
+        10: Creativity Demo Net Information\r
+        11: Official Distribution Site BBS List\r
+        12: How to Become a Distribution Site\r
+        13: The Brief History of The Future Crew\r
+        14: Answers to rumors\r
+        15: Sonic Dreams is NOT a Future Crew demo\r
+        16: Final Words\r
+\r
+\r
+ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿\r
+³1:                            OPENING WORDS                                  ³\r
+ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ\r
+\r
+        Welcome to the FCINFO.TXT file version 1.2 !\r
+\r
+       This textfile is a update to FCINFO10.TXT (version 1.0). The updated\r
+        parts are section 13 and the release list. In addition, voting form\r
+        has been removed.\r
+\r
+        This textfile was written to tell you about Future Crew, to\r
+        give you answers to most of the things you would probably like\r
+        to ask us, and to tell you how to get more demos.\r
+\r
+        If you are interested in us making a demo for you, please,\r
+        start reading from the next paragraph in this file.\r
+\r
+        The things discussed in this textfile are mainly aimed to\r
+        those people who have not seen much demos before, but are very\r
+        interested in learning more about them and about the whole\r
+        demo scene (=demo world) in general. In the future versions\r
+        there will be changes and additions taking into account what\r
+        has happened since the last information package.\r
+\r
+        Signed,         GORE\r
+\r
+\r
+ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿\r
+³2:                     DEMOS FOR COMMERCIAL PURPOSES                         ³\r
+ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ\r
+\r
+        If you find our demos interesting and would like us to make\r
+        you one for commercial purposes, do not hesitate to contact us.\r
+\r
+        When contacting us, please, include a short explanation of\r
+        what kind of a demo you are interested in. That would greatly\r
+        help us in evaluating the size of the project.\r
+        Kindly include, for example, these kinds of information:\r
+\r
+        - What kinds of demo effects would you be interested in\r
+        - Should there be any colorful still-pictures (logos, etc.)\r
+        - If the demo should have sound, which sound cards would you like\r
+          to be supported, what type of music should be played, etc.\r
+        - How big the demo could be in kilobytes and for how long\r
+          should the demo run in minutes approximately.\r
+        - Where would the demo be used and how soon would you like the\r
+          demo to be finished.\r
+\r
+        We would like you to understand that our demos are not animations.\r
+        This means that nearly everything you see on the screen is being\r
+        real-time calculated. The speed of the movement is usually\r
+        dependant to the speed of the VGA card and the speed of the\r
+        processor.\r
+\r
+        When contacting us, you should realise that we are all rather\r
+        young and thus still studying in various schools. This is why\r
+        our time is usually quite limited. And it is very likely that\r
+        we might already be involved in another project.\r
+\r
+        You should also know that we do not make demos for Microsoft\r
+        Windows due to its limitations from an assembly language\r
+        programming point of view.\r
+\r
+        Since normal mail is quite a slow way to communicate, we would\r
+        prefer the communication be made through e-mail or fax.\r
+\r
+        You can find our contact information from this file.\r
+\r
+\r
+ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿\r
+³3:                THE DISTRIBUTION AND USE OF OUR DEMOS                      ³\r
+ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ\r
+\r
+        All our demos, except the ones which we have created for different\r
+        companies, are freeware.\r
+\r
+        This means that you can copy and distribute them freely as long\r
+        as you make no modifications to them. Also, no money can be \r
+        charged for copying them.\r
+        \r
+        If you are a PD distributor, please contact us before including\r
+        our products in your collection.\r
+\r
+        In general, all commercial utilization of our demos without our\r
+        permission is forbidden. This includes selling disks containing\r
+        our demos.\r
+\r
+\r
+ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿\r
+³4:                         THE CURRENT MEMBERSTATUS                          ³\r
+ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ\r
+\r
+        Alias:          Real name:         Age:   Main responsibility:\r
+        --------------------------------------------------------------\r
+        GORE            Samuli Syvahuoko    20    Organizer\r
+        Psi             Sami Tammilehto     20    Coder\r
+        Trug            Mika Tuomi          21    Coder\r
+        Wildfire        Arto Vuori          18    Coder\r
+        Purple Motion   Jonne Valtonen      17    Musician\r
+        Skaven          Peter Hajba         18    Musician\r
+        Marvel          Aki Maatta          18    Graphics Artist\r
+        Pixel           Mikko Iho           18    Graphics Artist\r
+        Abyss           Jussi Laakkonen     18    BBS Coordinator\r
+\r
+        FC Internet Division:\r
+        Henchman        Markus Maki      - Thanks for helping with the e-mail\r
+        Jake            Jarkko Heinonen  - Thanks for providing the e-mail\r
+                                           address\r
+\r
+ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿\r
+³5:                     INTERNATIONAL DEMO COMPETITIONS                       ³\r
+ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ\r
+\r
+        For those who have no idea what the above are, I will explain.\r
+        Demo competitions (= parties) are international events where\r
+        the demo scene people go to meet each other and to compete in\r
+        the many competitions that are being held. These competitions\r
+        (= compos) are the demo, intro (= a demo sized under 100kb), \r
+        music and graphics. There are often different compos for different \r
+        machines (PC, Amiga, Atari ST and C-64). There are also prizes in\r
+        each compo (cash or computer hardware & software). The cash prizes\r
+        are usually the money people pay as the entrance fee (usually \r
+        about $20 US) and the possible computer hardware & software has\r
+        usually been sponsored by various computer companies. All \r
+        contributions are being experienced on a big screen (many meters\r
+        wide) and with the aid of a powerful audio system. After this all\r
+        the people or a selected jury vote and decide which contributions\r
+        are the best. After this the prizes are being given out and the \r
+        party is over. In the process people of course get to know each\r
+        other better and exchange a lot of new ideas.\r
+\r
+        All contributions are usually being released at the party itself,\r
+        but sometimes the PC demos are not. This is very unfortunate,\r
+        and will probably change in the future. The reason why this is\r
+        allowed to happen is becouse most demos haven't been beta-tested\r
+        well enough before the party and might not work on most machines.\r
+        So, the groups are being allowed to finish their demos after the\r
+        party and then release them when they so see fit.\r
+\r
+        Parties usually last for three days (a weekend) and are usually\r
+        organized by bigger demo groups.\r
+\r
+        There are a few big demo parties being held annually.\r
+        These include the following: The Party in Denmark at Christmas-\r
+        time, The Gathering in Norway around Easter, The Computer\r
+        Crossroad in Sweden before the summer and Assembly in Finland\r
+        in the end of Summer. The biggest of these is The Party, which\r
+        is being held for the third time this Christmas. And the most\r
+        recent party was Assembly'93, which was held for the second time.\r
+\r
+        A few months before the party, the organizing demo groups usually\r
+        release special invitation demos to advertise their parties.\r
+\r
+        At Assembly'93 there were a total of 1500 attenders from which\r
+        550 were PC people. About half of them had come from outside\r
+        Finland (Germany, Belgium, Holland, Sweden, Norway, USA, Israel,\r
+        Canada, Denmark, Switzerland, Spain, etc...). Only PC people were\r
+        allowed to vote on PC compos.\r
+\r
+        The overall quality of the contributions exceeded all expectations.\r
+        It was very cool to see how much the PC scene had developed since\r
+        last year. The party itself went quite smoothly, except for a\r
+        few bumps, but what would a demo party be without them... :-)\r
+        Also the prizes were very good in all PC compos. The total value\r
+        of all the prizes on the PC was about $7800 US.\r
+\r
+        Next we would like to thank all the companies which sponsored\r
+        most of the PC side prizes at Assembly'93:\r
+\r
+                Advanced Gravis, Canada\r
+\r
+                Epic MegaGames, USA\r
+\r
+                The Waite Group Press, USA\r
+\r
+                Terton, Finland\r
+\r
+                HiCompu, Finland\r
+\r
+                Toptronics, Finland\r
+\r
+                Pro Component, Finland\r
+\r
+                Lan Vision, Finland\r
+\r
+                Data Fellows, Finland\r
+\r
+\r
+        The thanks to all the sponsoring companies are also in the end\r
+        scroller of the demo. We hope to see you also next year!\r
+\r
+        And to all you people out there:\r
+\r
+        Don't forget to attend Assembly'94 next summer !\r
+\r
+\r
+ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿\r
+³6:               OFFICIAL ASSEMBLY'93 COMPETITION RESULTS                    ³\r
+ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ\r
+\r
+        Here we have the final and true results of the PC compos at\r
+        Assembly'93. Ignore all the anonymous 'result' files circulating\r
+        lately around BBS'es.\r
+\r
+        Assembly'93 party results for PC. Votes were calculated by giving five\r
+        points for the first place, four for the second place and so on. Up to\r
+        five contributions could be voted for. A total of 130 votes were cast.\r
+\r
+                                PC Demos Top Ten\r
+        -----------------------------------------------------------------\r
+        Place:  Votes:  #:  Group:                      Demo:\r
+        1.      472     10. Future Crew                 Second Reality\r
+        2.      403     9.  Silents                     Optic Nerve\r
+        3.      242     3.  Xography                    Elements\r
+        4.      126     2.  Dust                        Saga\r
+        5.      78      6.  Extreme                     Extermination\r
+        6.      51      5.  Virtual Visions             Fruits of Indolence\r
+        7.      31      7.  Paranoids                   Wasted Time\r
+        8.      26      4.  Alphaforce                  Phenomenon\r
+        9.      17      8.  Black Rain                  Obsession\r
+\r
+                                PC Intros Top Ten\r
+        -----------------------------------------------------------------\r
+        Place:  Votes:  #:  Group:                      Intro:\r
+        1.      378     8.  EMF                         Eclipse\r
+        2.      196     5.  Epical                      Tangle\r
+        3.      165     9.  Darkzone                    Debut\r
+        4.      163     7.  Onyx                        Locomotion\r
+        5.      125     10. Avalanche                   Motion\r
+        6.      115     15. Sonic-PC                    Plan-B\r
+        7.      106     6.  Doomsday prod.              Vanity & Apathy\r
+        8.      48      4.  Jeskola prod.               Dieetti-Intro\r
+        9.      43      3.  Surprise! prod.             Stardream\r
+        10.     12      1.  RatCompany                  Fraust\r
+\r
+                         PC Multichannel Music Top Ten\r
+        -----------------------------------------------------------------\r
+        Place:  Votes:  #:  Composer:                   Tune:\r
+        1.      219     7.  Skaven / Future Crew        Ice Frontier\r
+        2.      178     4.  Marvel / Future Crew        Can't remember you\r
+        3.      164     1.  Purple Motion / Future Crew Starshine\r
+        4.      153     5.  Leinad / Avalanche          Atomic II\r
+        5.      147     6.  Silent Mode / Pentagon      Inferno\r
+        6.      86      2.  Tonedeaf / Extreme          Heartbeat\r
+        7.      69      10. Prism / Wish                Time running out\r
+        8.      59      3.  Mikki / Epical              Opossumi\r
+        9.      56      9.  Funk't'ion / Paranoids      Deepness\r
+        10.     29      8.  Bloodsoaker / Wapy          Shout\r
+        \r
+                          PC 4-channel Music Top Ten\r
+        -----------------------------------------------------------------\r
+        Place:  Votes:  #:  Composer:                   Tune:\r
+        1.      133     19. Purple Motion / Future Crew Sundance\r
+        2.      98      13. Leinad / Avalanche          Teaspoon\r
+        3.      90      6.  Cybelius / Sonic-PC         Schwinging the Swing\r
+        4.      60      9.  Tonedeaf / Extreme          Sounds of War\r
+        5.      59      8.  Executioner                 Pork Chop\r
+        6.      53      17. Blizzard / Epical           Hidden Shadows\r
+        7.      51      21. JayJay / Progress           Phantoms\r
+        8.      47      5.  Mellow-D / Sonic-PC         Fast Changer II\r
+        9.      45      10. Gibson / Extreme            Blackbird\r
+        10.     43      16. Mistake / Darkzone          Michael Jackson sez hi!\r
+\r
+                               PC Graphics Top Ten\r
+        -----------------------------------------------------------------\r
+        Place:  Votes:  #:  Artist:                     Picture:\r
+        1.      176     8.  Marvel / Future Crew        Ice Kingdom\r
+        2.      144     2.  Delsion / Cascada           Eevi\r
+        3.      106     9.  Zenjuga / Black Mind        A3\r
+        4.      88      10. Pixel / Future Crew         Troll\r
+        5.      44      11. Giems / Dark Zone           Escaping from the Raytracer\r
+        6.      42      14. Ranx / Sonic-PC             Invintro\r
+        7.      29      13. PCA / Painkiller            W2\r
+        8.      22      7.  Kapsu / Epical              Assyroad\r
+                22      15. Mahlzahn / Pentagon         Dungeon\r
+        10.     19      12. Leinad / Avalanche          Korvmack\r
+\r
+\r
+ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿\r
+³7:                  QUICK INFORMATION ON THE PARTY 3                         ³\r
+ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ\r
+\r
+        As said before, The Party 3 will be the next big party.\r
+        And as usual, it will be held in Denmark. But this time it\r
+        will be held in Herning, the biggest exhibition centre in\r
+        scandinavia. There will of course be competitions for Amiga,\r
+        PC and C-64. The PC side is organized by ACCESS DENIED.\r
+        For more information, get your hands on the official PC scene\r
+        invitation intro (by Access Denied). The filename is ADPARTY.ZIP.\r
+\r
+\r
+ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿\r
+³8:                 HOW TO CONTACT THE FUTURE CREW                            ³\r
+ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ\r
+\r
+        Note that our mailing address has changed!\r
+\r
+        The new one is:                      Our home BBS is:\r
+\r
+        Abyss / Future Crew                  StarPort - FC WHQ BBS\r
+        (c/o Jussi Laakkonen)                +358-0-804 4626, 14.4k\r
+        Sepetlahdentie 2 E 36                +358-0-804 1133, 14.4k\r
+        02230  Espoo                         SysOp: Abyss\r
+        FINLAND\r
+\r
+        PLEASE NOTE THAT THE STARPORT'S #2 NODE NUMBER WAS _INCORRECT_\r
+        IN FCINFO10.TXT !! DO NOT CALL THAT NUMBER ANYMORE !!\r
+\r
+        You can also e-mail us or send a fax:\r
+\r
+        Internet:       jtheinon@kruuna.helsinki.fi  (GORE & Jake)\r
+\r
+        Fax:            +358-0-420 8620  (at GORE's place)\r
+\r
+        We receive a lot of mail and simply can't answer all of it.\r
+        Comments and opinions are always appreciated, but if you\r
+        also have questions, consider first if you might find the\r
+        answers elsewhere, for example from the Frequently Asked\r
+        Questions section inside this file. However, if you include\r
+        questions in your mail, please enclose a return envelope ready\r
+        with your address and an international mail coupon.\r
+        This would help us a lot.\r
+\r
+        The best and the fastest way to contact us is through e-mail.\r
+        So, if you really want to chat with us alot, you should find\r
+        a way to use e-mail. From internet you can also find lots of\r
+        demos and be able to e-mail other demo groups as well.\r
+        We get a LOT of e-mail so you may have to wait for our reply\r
+        for a while. We TRY to answer every e-mail we get but please,\r
+        write your e-mail address into your message.\r
+        \r
+        A very good anonymous ftp site where you can find lots of\r
+        demos is ftp.uwp.edu. Our demos can be found in the directory:\r
+        pub/msdos/demos/groups/future.crew.\r
+\r
+        You can also call our many BBSes around the world. You can\r
+        find the list of these BBSes in this textfile.\r
+\r
+\r
+ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿\r
+³9:            FREQUENTLY ASKED QUESTIONS ABOUT THE FUTURE CREW               ³\r
+ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ\r
+\r
+        Here we have compiled a list of questions along with the\r
+        answers (in random order) which are being asked in about\r
+        95% of all the letters we receive. Hopefully you will find\r
+        the answers to your questions from here and save us and\r
+        yourself from some unneeded paperwork.\r
+\r
+Q: Where can I get your and other groups' demos?\r
+A: There are several ways to get demos.\r
+   The best way (if you have a modem) is to call an FC distribution site\r
+   near you. They have all of our productions online and you can download\r
+   them freely. Also many normal BBSes carry our productions and other\r
+   groups' demos. If you don't have a modem, then getting our demos is a\r
+   lot harder. We don't have a mailswapping system. So, if you have a friend\r
+   who has a modem, why not try to get him to call one of our distribution\r
+   sites. Another VERY good way to get demos is from the INTERNET. A very\r
+   good demo site is ftp.uwp.edu which carries probably the best demo\r
+   collection on internet.\r
+\r
+Q: When is the musicdisk coming out ?\r
+A: We will probably release a sort of musicdisk at TheParty '93. It will\r
+   feature a long-awaited MOD/S3M-player for GUS/SB/SBPro and a nice pile of\r
+   Skaven's and Purple Motion's best S3M songs.\r
+\r
+Q: When is Scream Tracker 3.0 going to be out?\r
+A: Scream Tracker 3.0 is a product which might or might not ever be out.\r
+   This is very ambiguous, but the problem is that ST3 is not a high\r
+   priority project. The coder, Psi, is studying at a university, coding\r
+   demos, doing commercial software and trying to spend some freetime.\r
+   So at the moment there is no time to finish ST3 and no set release date.\r
+\r
+Q: When is Worldcharts issue #2 coming out?\r
+A: Since there are a lot of other groups publishing all kinds of magazines\r
+   today and our main directive is to make demos, and that Worldcharts #1\r
+   wasn't as good a success as we wanted it to be, we see no real sense in\r
+   in continuing to publish it anymore. Also as you might have guessed our\r
+   time has become too limited for these kinds of projects. In a nutshell,\r
+   at this time there is no real reason for you to send in your votes or\r
+   articles. If we change our minds about this, you can be sure that we'll\r
+   let you know. Thanks to everyone who supported us by sending us votes\r
+   and articles.\r
+\r
+Q: What programming books would you recommend to learn assembler and VGA?\r
+A: This is a hard question, and a general answer is, that any book will do.\r
+   You can get the basics from a book and books are a great reference,\r
+   but when it comes to creating something new, you can't just read it\r
+   from a book. We have all learned to code the hard way (a lot of\r
+   miscellaneous books and a lot of experimenting). Anyway, here are \r
+   some of the books we often find handy (there are undoutedly newer \r
+   prints, so check them out):\r
+\r
+        Mastering Turbo Assembler, Tom Swan\r
+                Hayden Books 1989, ISBN 0-672-48435-8\r
+        PC System Programming, Michael Tischer\r
+                Abacus 1990, ISBN 1-55755-036-0\r
+        The Programmers PC Sourcebook, Thom Hogan\r
+                Microsoft Press 1988, ISBN 1-55615-118-7\r
+        Programming the 80386, John H. Crawford and Patrick P. Gelsinger\r
+                Sybex 1987, ISBN 0-89588-381-3\r
+        Programmers guide to EGA and VGA cards, Richard F. Ferraro\r
+                Addison Wesley 1989, ISBN 0-201-12692-3\r
+\r
+   Also, most up to date are many software 'books', such as interrupt \r
+   lists from bbs'es and such. We have also found a lot of valuable\r
+   information in articles and such. In short, there is no magic\r
+   way of learning to code, it really does take hard work.\r
+\r
+Q: Are you going to make games in the future ?\r
+A: Why not. It all depends if we have the time. We have a few game\r
+   ideas cooking, but they are far from being completed. But we will\r
+   let you all know when we have a game coming, don't you worry!\r
+\r
+Q: What do the members of Future Crew do besides computers ?\r
+A: Most of us study in various schools; universities, high schools and\r
+   colleges. In real life most of us are quite normal(?) human beings.\r
+   Our hobbies are for example, sci-fi, movies, weight-lifting, techno,\r
+   hi-fi, etc, etc. And most of us have or has had a girlfriend.\r
+\r
+Q: What sound cards will you support?\r
+A: At the moment our productions support the following sound cards:\r
+\r
+        Gravis UltraSound   - for it's programming advantages\r
+        Sound Blaster Pro   - for being a standard\r
+        Sound Blaster       - same here\r
+\r
+   Support to other sound cards is always possible, but right now we\r
+   don't see enough demand to support any other cards.\r
+\r
+Q: Why do your demos require a 386 or higher to run?\r
+A: There are several reasons for the requirement; For example, 386 has many\r
+   new assembler commands, 32bit registers, and of course more processing\r
+   power. There isn't simply enough processing power in 286 to run a full\r
+   ledged demo. And besides, 286-based machines are a dying breed.\r
+   \r
+Q: How did you learn to code as you do now?\r
+A: Learning to code demos is a long and very very difficult process. It takes\r
+   years to learn to code demos very well. A good way to start is some high\r
+   level language like Pascal or C and then started to experiment with \r
+   assembler. It takes a lot of time and experimenting to get better, and\r
+   there are no shortcuts (for book recommendations, see a question before\r
+   this). The main thing is trying to understand what you do, then trying\r
+   to change the program to see what you get, and gain wisdom in what's\r
+   the best way of doing things. Learning to code well requires a lot of\r
+   patience, a lot of enthusiasm and a lot of time. It is not easy.\r
+\r
+Q: What programs do you use to do your demos?\r
+A: We use the following programs to do our demos; For code we use \r
+   Borland C++, Microsoft C, Borland Pascal and of course TASM (Turbo\r
+   Assembler). For graphics we use Deluxe Paint 2 Enchanded (and 3D Studio\r
+   2.0). For making the music we use Scream Tracker 3.0 beta, and for \r
+   digitizing the samples for our songs we use Advanced DigiPlayer 2.5\r
+   beta. Scream Tracker 3.0 and Advanced DigiPlayer are our own programs\r
+   made by Psi, and they are not available to the public at this time.\r
+   In addition to all these, we of course have a big collection of \r
+   utilities we have crafted to our need during the years.\r
+   \r
+Q: I'm a beginner programmer. I wonder if you could help me learn demo coding?\r
+A: To help beginners learn the secrets of democoding we have released the\r
+   full source of our Mental Surgery demo. This source code is spread along\r
+   with our STMIK (Scream Tracker Music Interface Kit), which is a 4 channel\r
+   music player, which you can link into your own programs. You can find these\r
+   from our distribution sites, under the name STMIK020.ZIP (be sure to grab\r
+   STMIKFIX.ZIP too, which fixes one nasty bug). Do not try to ask us send\r
+   you some of our unreleased source code.\r
+   If you are reading this file, you probably know already that we have\r
+   released a new source code pack which includes the full, documented\r
+   ASM source code of our new StarPort intro II.\r
+   There's always the possibility that we will release some other source code\r
+   in the future as well, but at this time there are no immediate plans for\r
+   such an event.\r
+\r
+Q: What is the complete list of your released productions with release dates?\r
+A: To date, we have released the following productions:\r
+\r
+   Filename        Size   Released   A Short Description\r
+   --------        ----   --------   -------------------\r
+   YO!.ZIP         32 kb   2-24-89   YO! intro, VGA textmode/PC-speaker\r
+   GR8.ZIP         31 kb   7-12-89   GR8 intro, EGA/No sound\r
+   FC-SLIDE.ZIP   350 kb   7-23-90   Slideshow I, a graphics collection, SB\r
+   ST224.ZIP      130 kb   2-22-91   Scream Tracker 2.24 shareware version, SB\r
+   MENTAL.ZIP      90 kb   7-02-91   Mental Surgery demo, SB/Covox/PC-speaker\r
+   STMIK020.ZIP   170 kb   8-10-91   Scream Tracker Music Interface Kit 0.20\r
+   FISHTRO.ZIP    230 kb   4-08-92   Assembly'92 invitation intro, SB\r
+   STMIKFIX.ZIP    10 kb   7-14-92   A Bugfix to STMIK\r
+   UNREAL.ZIP    1350 kb   8-06-92   Unreal megademo, SB/SBp\r
+   STARPRT2.EXE     6 kb   9-13-92   StarPort BBS intro, VGA/AdLib\r
+   THEPARTY.ZIP   165 kb  10-02-92   The Party II invitation intro, SB/SBp\r
+   PANIC.ZIP      950 kb   2-04-93   Panic trackdemo, SB/SBp\r
+   ASM-93.ZIP     400 kb   6-15-93   Assembly'93 invitation intro, SB/SBp/GUS\r
+   WCHARTS.ZIP    680 kb   6-26-93   Worldcharts magazine issue #1, SB/SBp/GUS\r
+   SOULOMAT.ZIP   100 kb   7-10-93   A song by Purple Motion\r
+   ICEKNGDM.LBM    65 kb   8-01-93   Winner of PC graphics compo at Asm'93\r
+   ICEFRONT.ZIP   180 kb   8-01-93   The winner of PC multichnl compo at Asm'93\r
+   CAN'T.ZIP      125 kb   8-01-93   The second in PC multichnl compo at Asm'93\r
+   STRSHINE.ZIP   225 kb   8-01-93   The third in PC multichnl compo at Asm'93\r
+   TROLL.LBM       85 kb   8-01-93   The fourth in PC graphics compo at Asm'93\r
+   SUNDANCE.ZIP   235 kb   8-10-93   The winner of PC 4chnl compo at Asm'93\r
+   2NDREAL1.ZIP  1250 kb  10-07-93   Second Reality, Asm'93 winner, SB/SBp/GUS\r
+   2NDREAL2.ZIP   790 kb  10-07-93   Second part of the Second Reality demo\r
+   2NDR_MS.ZIP    280 kb  11-01-93   Skaven's songs from Second Reality\r
+   SYMPHONY.ZIP   260 kb  11-01-93   Symphony by Skaven \r
+   PMFRACT.ZIP    210 kb  11-05-93   The winner of Megaleif ST/PC music compo\r
+   BUSMATKA.ZIP    75 kb  11-09-93   Finnish invitation to Party3 bussymatka\r
+   STARPORT.ZIP  4522 byt 11-21-93   StarPort BBS intro II, VGA/Adlib\r
+   SP2SRC.ZIP      30 kb  12-02-93   StarPort BBS intro II sources\r
+\r
+   You SHOULD be able to find all of the above from our Distribution Sites.\r
+\r
+Q: Exactly where do FC members study and what?\r
+A: Many of us study in high school or in university. Here is the complete list:\r
+\r
+        Psi             - Turku university, major informatics\r
+       Trug            - finished his studies\r
+       WildFire        - last year in high school\r
+       Purple Motion   - second year in high school\r
+       Skaven          - not studying at the moment\r
+       Pixel           - last year in high school\r
+       Marvel          - last year in high school\r
+       Abyss           - last year in high school\r
+        GORE            - studying in business school\r
+\r
+Q: How long does it take to make a demo like Second Reality?   \r
+A: The complete time that it takes to make such demo can't really be counted.\r
+   Most of our knowledge is based on years of hard work and on our previous\r
+   works. All of us do little experiments on their freetime and when a \r
+   "critical mass" is achieved the making of a demo begins more seriously.\r
+   From this point to a final demo (in the case of a major production like\r
+   Second Reality) it takes around three to six months.\r
+   \r
+   \r
+ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿\r
+³10:                  CREATIVITY DEMO NET (CDN) INFORMATION                   ³\r
+ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ\r
+\r
+  Creativity Demo Net or shortly CDN is nowadays quite a common sight\r
+among BBS'es that are oriented towards demos. But what exactly is CDN?\r
+\r
+  The idea behind The Creativity Demo Net(work) is now about 2 years\r
+old. I had been dreaming about having a way to communicate electronically\r
+between different demogroups. When we (Future Crew) attended Megaleif\r
+Easter Party'92 last year in Uppsala, Sweden, I was positively surprised\r
+when I found out that Mirage / Cascada had also been thinking about the\r
+same thing. We both thought that it was a good idea and began developing it.\r
+\r
+  But it didn't work out as we intended. There were a lot of difficulties,\r
+in Sweden and here in Finland. At first we tried to spread the net via FidoNet,\r
+but soon it came clear to us that demogroups needed their own net. The\r
+same time I had been also talking with Trojaner (SysOp of Skull's Southern\r
+Germany HQ) and he was also inspired by this idea. We decided that Skull\r
+and Future Crew wouldn't be enough to start a new net with, so I contacted\r
+Arjan Pool (who had relations with DCE) and he also thought that the idea\r
+was just great. And we got underway.\r
+\r
+  At first the net was called just plainly DemoNet, but it was almost \r
+immediately changed to Creativity Demo Net. Anyway, at first it was planned\r
+that StarPort would become the World HQ, but as Arjan wanted to take the job\r
+and all the big responsibilities, Arco BBS became the WHQ (and still is). Much\r
+of the coming success of CDN was based on Arjan's continuing hard work for CDN.\r
+\r
+  The net started working in August 1992, four months after the first idea\r
+about a demonet had come to me. And after that the net has spread like a\r
+wildfire! At first CDN spanned only 3 countries (Finland, Holland and Germany)\r
+but soon Sweden joined in, and then country after country and bbs after bbs\r
+joined in. To this date CDN spans the following countries: Finland, Holland,\r
+Germany, Switzerland, Spain, Denmark, Sweden, England, Italy, Turkey, Belgium,\r
+Canada, USA, France, Hungary, Brazil, Austria and Australia. THAT'S 18 \r
+COUNTRIES! And there are about 140 nodes in CDN, all BBS's that are demogroup's\r
+BBS'es. Considering the small amount of demogroup BBS'es, I would estimate\r
+that about 75% of all demogroup BBSes are connected to CDN and all of the\r
+biggest groups like FC, Triton, Renaissance, Cascada, etc... are connected\r
+to the net.\r
+\r
+  So what kind of echoes does CDN carry? Well here is the complete list of\r
+echomail areas:\r
+  \r
+*    1. CDN.4ALL\r
+        The area for everyone in CDN\r
+*    2. CDN.ANNOUNCE\r
+        Made a new demo ? announce it overhere.\r
+*    3. CDN.DISKMAG\r
+        All information about diskmagazines\r
+%    4. CDN.CHAT\r
+        All chatting with other members\r
+%    5. CDN.PROGRAMMING\r
+        For help with programming problems\r
+%    6. CDN.GFX\r
+        For all graphics makers\r
+%    7. CDN.MUSIC\r
+        MIDI/MOD/MUSIC help and questions\r
+S    8. CDN.TEST\r
+        Test area\r
+!    9. CDN.INTERGROUP\r
+        For selected groups within CDN\r
+S   10. CDN.SYSOP\r
+        Sysops CDN only\r
+H   11. CDN.HQ_HOST\r
+        For mail between HOSTS versus HQ\r
+\r
+* - for everyone who gets\r
+    connected to a BBS\r
+% - for registered persons\r
+    (demogroup members)\r
+S - only for CDN sysops\r
+H - only hosts and HQ\r
+! - for special selected groups\r
+\r
+  CDN has areas for relaxed talk between people (and it gets QUITE\r
+relaxed sometimes, and QUITE weird =), but it's just fun!), and for\r
+serious purposes such as programming.\r
+\r
+  And what does CDN require from a BBS? Well, the first and MOST\r
+important requirement is that the BBS is some demogroups (preferably an\r
+active one) BBS. That is rule that there are only few exceptions from.\r
+But otherwise, you just have to:\r
+\r
+ - place the completed files of the CDN on his/her BBS that everyone\r
+   can download them\r
+ - use the CDN nodelist and it's updates\r
+ - connect to every area available to them\r
+\r
+  Not too many rules... And that is because we want CDN to be fun,\r
+not some playground for idiots with a lawbook for brains.  \r
+Also, there are ABSOLUTELY no charges in CDN, so the only costs you have to \r
+pay are your own phonebills.\r
+\r
+  You can FREQ more info about from for example the WHQ under the magic name\r
+CDNINFO. So, get more info now if you are interested in joining in!\r
+\r
+                            =ABYSS- / Future Crew\r
+\r
+\r
+ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿\r
+³11:              OFFICIAL FUTURE CREW DISTRIBUTION SITES                     ³\r
+ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ\r
+\r
+ÚÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿\r
+³Country   ³BBS name              ³BBS number(s)           ³SysOp / Other info³\r
+ÃÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´\r
+³Finland   ³StarPort - FC WHQ     ³+358-0-804-4626 HST/V32b³=ABYSS- / FC      ³\r
+³          ³                      ³+358-0-804-1133 V32bis  ³                  ³\r
+³          ³                      ³                        ³                  ³\r
+³Australia ³Tequila Sunrise       ³+61-7-801-4446  V32bis  ³Bartender         ³\r
+³          ³                      ³                        ³                  ³\r
+³Austria   ³Polymorph LIGHTS      ³+43-1-596-9026  V32b&HST³Gery              ³\r
+³          ³                      ³                        ³                  ³\r
+³Belgium   ³Genesis               ³+32-2-2453498   16.8k   ³McGarret&MadFlight³\r
+³          ³                      ³                        ³                  ³\r
+³Belgium   ³Point Break           ³+32-11436925    16.8k   ³Lord Cyrix &      ³\r
+³          ³Access Denied WHQ     ³                        ³Jumping Jack Flash³\r
+³          ³                      ³                        ³                  ³\r
+³Brazil    ³Warmboot BBS          ³+55-19426-5112  V32b    ³Carlos Cantu      ³\r
+³          ³                      ³                        ³                  ³\r
+³Canada    ³Spasm-o-Tron          ³+1-514-744-5718 V32bis  ³Snibble / HiTS    ³\r
+³          ³                      ³                        ³                  ³\r
+³Canada    ³The Basement Breweries³+1-905-527-3469 V32bis  ³Wizard            ³\r
+³          ³                      ³                        ³                  ³\r
+³Denmark   ³Crack Central BBS     ³+45-981.10096   19.2k   ³Executioner       ³\r
+³          ³                      ³                        ³                  ³\r
+³England   ³Sound & Vision BBS    ³+44-932-252323  V32bis  ³Rob Barth         ³\r
+³          ³                      ³                        ³                  ³\r
+³Germany   ³The BitBlasters BBS   ³+49-851-83994   16.8k   ³BitBlaster        ³\r
+³          ³                      ³                        ³                  ³\r
+³Germany   ³The Continental BBS   ³+49-711-548501  16.8k   ³Trojaner          ³\r
+³          ³                      ³                        ³                  ³\r
+³Holland   ³The Consultation BBS  ³+31-1170-54987  V32bis  ³Preceptor         ³\r
+³          ³                      ³                        ³                  ³\r
+³Hungary   ³Dune II               ³+36-62-342-793  V32bis  ³TSC / Phantom     ³\r
+³          ³                      ³open: workdays 14-07 CET³weekends: 24h     ³\r
+³          ³                      ³                        ³                  ³\r
+³Iceland   ³Mori BBS              ³+354-1-677020   V32bis  ³Arni Eggertsson   ³\r
+³          ³                      ³                        ³                  ³\r
+³Israel    ³The Bureaucratic BBS  ³+972-9-984173   V32bis  ³Shachar Cafri     ³\r
+³          ³                      ³+92-9-426657    V22bis  ³                  ³\r
+³          ³                      ³                        ³                  ³\r
+³Norway    ³Romeo November        ³+47-4-536698    V32bis  ³Stinger           ³\r
+³          ³                      ³+47-4-536797    19.2k   ³                  ³\r
+³          ³                      ³                        ³                  ³\r
+³Singapore ³MultiMedia GS         ³+65-252-1220    V32b    ³WildCat           ³\r
+³          ³                      ³                        ³                  ³\r
+³Spain     ³Dracker BBS           ³+34-3-385-3393  16.8k   ³Gvyt / ENiAC      ³\r
+³          ³                      ³                        ³                  ³\r
+³Sweden    ³Illusion              ³+46-18-260565   V32bis  ³ZED / FAiC        ³\r
+³          ³                      ³                        ³                  ³\r
+³Switzerlan³Wonderland            ³+41-64-47-3046  16.8k   ³PfUsuUS           ³\r
+³          ³                      ³                        ³                  ³\r
+³USA, NY   ³The Sound Barrier     ³+1-718-979-6629 HST V32b³Daredevil / REN   ³\r
+³          ³Renaissance WHQ       ³+1-718-979-9406 V22bis  ³Charles Scheffold ³\r
+³          ³                      ³                        ³                  ³\r
+³USA, ND   ³Quantum Accelerator   ³+1-701-258-0319 V32bis  ³Chris Zimman      ³\r
+³          ³                      ³                        ³                  ³\r
+³USA, TX   ³Programmer's Oasis    ³+1-214-328-6142 V32bis  ³Daniel Potter /   ³\r
+³          ³                      ³                        ³Digital Infinity  ³\r
+³          ³                      ³                        ³                  ³\r
+³USA, SC   ³The End of Time       ³+1-803-855-0783 V32bis  ³Holy Water and    ³\r
+³          ³                      ³                        ³The Hit Man       ³\r
+³          ³                      ³                        ³                  ³\r
+³USA, KY   ³Eleutheria            ³+1-606-223 1853 V32bis  ³Soul Rebel /      ³\r
+³          ³                      ³                        ³Avalanche         ³\r
+³          ³                      ³                        ³                  ³\r
+³USA, MO   ³Red Sector            ³+1-816-792 3821 16.8k   ³Lion Heart        ³\r
+³          ³                      ³+1-816-792 2029 HST     ³                  ³\r
+³          ³                      ³                        ³                  ³\r
+³USA, D.C. ³Data Connection BBS   ³+1-703-506 8598 16.8kHST³Ryan / Renaissance³\r
+³          ³                      ³                        ³                  ³\r
+³USA, FL   ³The Power Grid        ³+1-813-481-6539 16.8k   ³Grid Runner &     ³\r
+³          ³HQ for many groups    ³                        ³Syntax Error / iCE³\r
+ÀÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ\r
+\r
+        In addition, you can get our demos from internet where\r
+        a very good anonymous ftp demo site is ftp.uwp.edu. Our demos\r
+        can be found in the directory: /pub/msdos/demos/groups/future.crew.\r
+\r
+ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿\r
+³12:              HOW TO BECOME A FUTURE CREW DISTRIBUTION SITE               ³\r
+ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ\r
+\r
+         We are looking for distribution sites around the world.\r
+         We are looking for demo-oriented BBS'es that are interested\r
+         in becoming part of FC's growing number of BBS'es.\r
+\r
+         So, what does it take to become an FC distsite?\r
+         In fact, it's not easy, we require a lot, but before\r
+         giving up, take a look at the following list:\r
+         \r
+                 - Your BBS MUST have every single one production\r
+                  FC or any member of FC has ever RELEASED\r
+                \r
+                - Your BBS has to call StarPort (FC WHQ) at least\r
+                  twice a month, and keep in contact with the FC\r
+                  \r
+                - Your BBS also has to be a voting place for our possibly\r
+                  continuing Worldcharts diskmag (voting door)\r
+                  \r
+                - Your BBS has to be absolutely DEMO-ORIENTED, NOT\r
+                  some gigantic all-around BBS. YOU yourself have\r
+                  to be very interested about demos and the PC demo\r
+                  scene in general\r
+                  \r
+                - Your BBS should join the Creativity Demo Net, if\r
+                  by any means possible\r
+                  \r
+                - Your BBS would also be a Future Crew information\r
+                  forum. You would have to answer questions concerning\r
+                  FC and our production, and help people who have\r
+                  problems with our software\r
+                  \r
+                - Your BBS should be operated on a PC compatible,\r
+                  with at least a 14400 BPS modem and 300 megabytes\r
+                  of diskspace for demos, and the BBS should be open\r
+                  24 hours a day, and 365 days / year\r
+\r
+        So what do you get in exchange? Well, these things we can\r
+        guarantee:\r
+        \r
+                - Your BBS will be mentioned in every FC production\r
+                  in the distsite BBS list\r
+                - You have a chance to get all FC's future productions\r
+                  first hand\r
+                - You will get some FC inner circle information\r
+                \r
+        What we can't guarantee, but what is likely to happen, is that\r
+        your BBS will become more and more popular and it's quality\r
+        will improve dramatically. \r
+        \r
+        Remember that we already have BBSes in most of the european\r
+        countries (check out the BBS list), but there are still some\r
+        gaps left which we'd like to fill out. In the USA and Canada,\r
+        we are accepting one BBS per state.\r
+\r
+        Please read the above rules carefully and think twice before\r
+        sending in the application below:\r
+\r
+-----8<------8<------8<------8<---cut-here------8<------8<------8<------8<-----\r
+\r
+\r
+        THE FUTURE CREW DISTRIBUTION SITE APPLICATION FORM\r
+        ==================================================\r
+\r
+        Copy this application to it's own file, fill it out and give the\r
+        file the name of your BBS. Then send it to StarPort or e-mail it.\r
+        Do NOT fax it or send it by normal mail!\r
+\r
+        BBS name                 :______________________________________\r
+         \r
+        BBS phonumber(s)         :______________________________________\r
+                                 :______________________________________\r
+                                 :______________________________________\r
+                                 :______________________________________\r
+        \r
+        BBS modem(s)             :______________________________________\r
+                                 :______________________________________\r
+                \r
+        Modem speeds supported   : [ ] 1200  [ ] 2400  [ ] 9600 (V32)\r
+        (place X on appropriate  : [ ] 14.4k (V32bis)  [ ] 16.8k\r
+        box)                         : [ ] MNP   [ ] V42bis\r
+\r
+        BBS net address(es)      :______________________________________\r
+        \r
+        List networks you are in :______________________________________\r
+                                 :______________________________________\r
+\r
+        Would you be willing to join the Creativity Demo Net if you aren't\r
+        yet in?                  : (Yes / No)\r
+        \r
+        If necessary would you be willing to become a Host / Hub for The\r
+        Creativity Demo Net?     : (Yes / No)\r
+        \r
+        BBS software             :______________________________________\r
+        \r
+        Mailer software          :______________________________________\r
+        \r
+        Is your board any other group's distsite or member board: (Yes/No)\r
+        If yes, please list them :______________________________________\r
+                                 :______________________________________\r
+                                 :______________________________________\r
+\r
+        How many lines/nodes does your system have :____________\r
+        \r
+        How many users does your system have :__________________\r
+        \r
+        How large (in MB's) is your system   :__________________\r
+        \r
+        Is your BBS very demo-oriented : (Yes / No)\r
+                                 \r
+        In what country do you live :___________________________________\r
+        \r
+        SysOp alias / group      :______________________________________\r
+        \r
+        SysOp real name          :______________________________________\r
+        \r
+        SysOp voice phone number :______________________________________ \r
+        \r
+        SysOp e-mail address     :______________________________________\r
+\r
+        SysOp age                :___        \r
+\r
+        SysOp full mail address  :______________________________________\r
+                                 :______________________________________\r
+                                 :______________________________________\r
+                                 :______________________________________\r
+                                 \r
+\r
+        Anything special we should be aware of?:\r
+        _________________________________________________________________\r
+        _________________________________________________________________\r
+        _________________________________________________________________\r
+        _________________________________________________________________\r
+        _________________________________________________________________\r
+        \r
+        \r
+-----8<------8<------8<------8<---cut-here------8<------8<------8<------8<-----\r
+\r
+        P.S. Filling up this form doesn't mean that you will automatically\r
+        become an FC distribution site! We'll check the form and get back\r
+        to you!\r
+\r
+\r
+ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿\r
+³13:               THE BRIEF HISTORY OF THE FUTURE CREW                       ³\r
+ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ\r
+                \r
+                    by Abyss and Gore / Future Crew\r
+\r
+ - 1986-1987 -\r
+  Future Crew (FC) was founded in the year 1986 on the C-64. And only one\r
+ member has been in the group for the whole time - Psi. FC did two\r
+ demos on the C-64 before changing into the PC scene in the year 1988.\r
+\r
+ - 1988 -\r
+  FC's first PC demo was a CGA sinus -scroller called GR8. At that time\r
+ the members were HAL, JPM, SS (Psi) and SIDDER. And DARK POWER\r
+ was FC's BBS.\r
+\r
+ - 1989 -\r
+  Then there came YO! which was quite popular for a while. It used one of\r
+ the VGA's textmodes and included 'nice' PC-speaker music. It had\r
+ many scrollers, a sinusing YO!-logo, a little bouncing ball and\r
+ a 2D-starfield. At this time ICE joined and so FC\r
+ had another BBS - SILICON DRAGON.\r
+\r
+ - 1990 -\r
+  In the year 1990 there was only one demo release from us, the Slideshow I.\r
+ It was the first PC demo which included 4 voice SoundBlaster music.\r
+ It didn't include any other special code for it was a VGA picture\r
+ slideshow. And at this time there were a lot of members in FC:\r
+ Psi, ICE, HAL, JPM, SID, BIG, DAC, MAC and SEBU.\r
+\r
+ - 1990 -\r
+  And only shortly after Slideshow I, Psi released his ScreamTracker 2.0 -\r
+ a 4 voice music editing program inspired by the Amiga SoundTracker.\r
+ ST 2.0 was a real success. But of course, it didn't take much time\r
+ when a pirated version was on the move. This was in the year 1990.\r
+\r
+ - 1991 -\r
+  In summer 1991, FC released a demo called Mental Surgery. It had\r
+ a big scroller on the top of the screen, 3D-starfield, a nice writer,\r
+ music scopes and of course 4 voice SoundBlaster music.\r
+ This was the last FC demo that worked on a 286 machine. At this\r
+ time the members were: Psi, ICE, Dr.Venkman and Purple\r
+ Motion. And only a while after this I (GORE) joined FC and ICE lost\r
+ the interest to demos and left FC along with his BBS. And\r
+ Dr.Venkman went crazy by selling his computer and retired for a while.\r
+\r
+ - 1992 -\r
+  So, FC lived quietly for about half a year. But when the year\r
+ 1992 came Trug, Pixel, Skaven and Abyss joined FC. And as Abyss\r
+ joined, FC had a BBS again, namely StarPort. So, in the\r
+ beginning of the year 1992 FC had the following members:\r
+\r
+ 1. Psi --- Main coder\r
+ 2. Trug --- Asst. coder\r
+ 3. GORE --- Organizer/asst. GFX-man\r
+ 4. Pixel --- Main GFX-man\r
+ 5. Abyss --- BBS support/utilities\r
+ 6. Skaven --- Musician/asst. GFX-man\r
+ 7. Purple Motion --- Musician\r
+\r
+  It was at this time that we had begun making UNREAL. Our first\r
+ plan was to release it at MEGA-Leif Convention - An Atari ST/PC party\r
+ held in Uppsala, Sweden. But about a month before MEGA-Leif,\r
+ MeeGosh/Rebels (Amiga) called me and told me about ASSEMBLY'92\r
+ and that it would be cool to have also the PC scene there. So, he\r
+ asked us to do an invitation intro for the PC scene about this\r
+ mega-event. We agreed and so, UNREAL was put to rest as Psi got\r
+ the idea of making something different - namely the Fishtro.\r
+ It took us about two weeks to create Fishtro from nothing, but\r
+ when we went to MEGA-Leif Convention, we still had little bugs in it and\r
+ therefore we couldn't release it until a week after MEGA-Leif. \r
+ We also competed with Fishtro in the MEGA-Leif PC demo compo, but\r
+ we were never told who came second. As the people who were at MEGA-Leif\r
+ remember, the belgian Raiders Brothers won the demo compo, but\r
+ they have not released their winning demo to this date (13.7.92).\r
+ After we came back from MEGA-Leif, we started on making UNREAL again.\r
+ And Dr.Venkman came back from his retirement.\r
+\r
+ - 1992 -\r
+   Then Unreal was released. Unreal was the first really big megademo for PC and\r
+ it hit the top of the charts immediately.\r
\r
+ - 1992 -\r
+   Then we were contacted by the organizers of a BIG Amiga/C64/PC party, called\r
+ The Party 1992. They asked us to organize the PC demo compo there and make\r
+ again an Invitation Intro for it's PC side. So The Party 1992 Invitation Intro\r
+ was made. At that time we had the following members:\r
\r
+ Psi            - Code\r
+ Trug          - Code\r
+ WildFire      - Code\r
+ Pixel         - GFX\r
+ Purple Motion - Music\r
+ Skaven                - Music & GFX\r
+ GORE           - Organizer\r
+ Abyss         - BBS support\r
\r
+  The Party 1992 Inv. Intro was mostly coded by Psi and WildFire. WildFire was\r
+ our new coder who joined us in autumn 1992. He had before been active on the\r
+ Atari ST scene.\r
\r
+ - 1992 -\r
+   Then it was the time for another big demo. The making of Panic! began.\r
+ It was the normal process of making demos with blood and sweat and annoying\r
+ deadlines. WildFire was the one to assemble the demo together, but lots of\r
+ code was also done by Psi and Trug.\r
\r
+  Then it was the time for The Party 1992. As we thought that it would really \r
+ nice to get as many people as possible to The Party as cheaply as possible,\r
+ we decided to organize a bus trip there with the amiga people. So we managed\r
+ to load two buses full of computer freaks and take our leave towards The\r
+ Party. At that time The Party 1992 was the biggest computer party ever.\r
+ There were about 2500 computer freaks of which around 400 were PC dudes.\r
\r
+  There we entered the demo compo with Panic, and to our surprise we came\r
+ second. Witans Facts of Life had won the demo compo. We were quite \r
+ disappointed by this, because there was absolutely no voting. The voting \r
+ system on Amiga just didn't work. And then some organizer just asked the last\r
+ remaining PC organizer "What do you think were the best demos" without telling\r
+ him that these were going to be the official results. And without thinking he\r
+ just said "Witans, FCs and Sonics". \r
\r
+  Anyway, The Party 1992 was a big success.\r
\r
+ - 1993 -\r
+   After The Party 1992 we lived quietly for awhile. The only big change was\r
+ that Marvel (formerly from Sonic-PC) joined us. So we now have two gfx \r
+ artists. Then we began thinking of making a diskmag. At first nobody really\r
+ wanted to code it, so we thought that we would make it as a co-operation \r
+ with Stone (a finnish demogroup). But after some co-operation troubles we\r
+ began making it 100% by ourselves. We tried to make it the best diskmag\r
+ on the PC and according to many opinions, we succeeded quite well. What\r
+ we tried to do, was to set an example on how well you can do diskmags if\r
+ you really try. The diskmag was coded by Psi and the GFX were done by\r
+ Pixel and the musics by Purple Motion and C.C.Catch from Renaissance.\r
\r
+ - 1993 -\r
+   Then it was the time for Assembly'93. Once again we were the PC organizers\r
+ and we made an invitation intro for it. It's name is quite easy to guess,\r
+ it's Assembly'93 Invitation Intro (hard one! =)). It was coded by Trug,\r
+ the GFX were done by Marvel and the musics by Purple Motion. It fulfilled\r
+ its purpose (to get as many people as possible to Asm'93) very well.\r
\r
+  Assembly'93 was the biggest ever summer demo party. There were about 1300\r
+ people on the party place of which around 450 were PC demo freaks. Asm'93\r
+ was also a big advancement on the PC side. For the first time we also had\r
+ a intro, music (4 channel and multichannel) and graphic compos. \r
\r
+  Our biggest production yet, the Second Reality won the PC demo competition.\r
+  You have most probably also seen it, so I won't (again) go into detail in\r
+  trying to describe its effects.\r
+  \r
+  At the moment we are looking ahead to The Party III: The Ultimate. We are\r
+again organizing a bustrip to Herning (were the party is to take place). We\r
+really recommend this party because we feel that The Party III is going to\r
+be the biggest and coolest demo party for PC ever. So be there or be square!\r
+\r
+\r
+ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿\r
+³14:                       ANSWERS TO RUMORS                                  ³\r
+ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ\r
+\r
+\r
+       Rumors:   - The computer was changed to a faster one to run Second\r
+                   Reality.\r
+                 - An additional GUS was added to machine.\r
+                 - The VGA card was changed to a faster one.\r
+                 - Marvel scanned his picture (Ice Kingdom)\r
+                 - Marvel didn't compose his tune "Can't remember you"\r
+                 - FC did something to the tunes, because they sounded so\r
+                   weird.                  \r
+                 - FC used dirty tricks in the The Party II\r
+                 - FC skipped some parts of other groups demos to hurt their\r
+                   score\r
+                 - FC conducted a ballot-stuffing (fake voting)\r
+\r
+        Question 1) Was the computer changed?\r
+        Answer   1) No. All the competitions (music,gfx,intro and demo) were\r
+                    run on the same 486/33mhz 64kb cache GUS 1mb and ET4000\r
+                    1mb machine with 4mb of RAM. This machine belongs to me\r
+                    (Abyss) and is the very same machine (except for the GUS)\r
+                    which was used to display the demos at The Party II.\r
+\r
+\r
+        Question 2) Did you change the VGA card?\r
+        Answer   2) No we didn't. The same ET4000 1MB VGA card was used all\r
+                    the time.\r
+\r
+\r
+        Question 3) Was a second GUS card added to the machine?\r
+        Answer   3) At first few months before the Asm'93 we thought that\r
+                    Dolby Surround Pro Logic was only possible to make\r
+                    if you had 2 GUSes. Then we found out that it is very\r
+                    easy and possible to do with only one GUS card. So no\r
+                    second GUS card was added.\r
+\r
+\r
+        Question 4) Why is Marvels Ice Kingdom so like BEAR1.GIF?\r
+        Answer   4) Let me explain at first about the background. Most of\r
+                    you arent familiar with the Amiga scene. On the amiga\r
+                    scene it is forbidden to scan a picture, but it is\r
+                    ok to use a existing picture as a model from which to\r
+                    draw. What this means is that many of pictures made\r
+                    are not ORIGINALLY created by the author (for example,\r
+                    EEVI which came second at Asm'93 is originally by H.\r
+                    Giger (the guy who did the gfx for Alien (I-III) for\r
+                    example)).\r
+                    What Marvel did was, that he draw the outlines from the\r
+                    BEAR1.GIF and the proceeded on his own with the most\r
+                    difficult task. If you compare BEAR1.GIF and ICEKNGDM.LBM\r
+                    1) they are in different resolutions\r
+                    2) there is no wall in the ICEKNGDM.LBM\r
+                    3) if you zoom in the picture you will see that the\r
+                       colouring (dithering) of the picture is completely\r
+                       different than in Marvels picture.\r
+                    4) BEAR1.GIF looks scanned, it looks helluva good and\r
+                       it looks very different than Marvels picture.\r
+\r
+\r
+       Question 6) Did Marvel compose the tune "Can't remember you"\r
+       Answer   6) Yes, he did. Among his other talents, Marvel is a quite \r
+                   good composer. He has made around 5-6 tunes during his\r
+                    amiga career (though not too famous songs). He composed\r
+                    the "Can't remember you" using ST ]I[ beta.\r
+                   \r
+\r
+       Question 7) Did you refuse to use any other player than ST3?\r
+       Answer   7) No we didn't. Most of the songs were supplied to us as\r
+                   plain MOD files. No player was included with them. Only\r
+                   one song had it's own player, and that player was used\r
+                   to play it. In the Assembly'93 text file there was a \r
+                   notion:"Bring your own player" (about the PC multichannel\r
+                   competition). Because no player was supplied with most of\r
+                   the MODs/multichannel files, we used the best player\r
+                   we know of, the ST3 beta.\r
+                   It is also claimed by people who have never used nor\r
+                   seen ST3 that ST3 has still serious bugs in its .MOD\r
+                   capabilities. This can't be more wrong as ST3 is one of\r
+                    the very few composers that really play all Amiga commands\r
+                   really correctly, not like many PC composers. So it's more\r
+                   likely that composer used to create the tune wasn't enough\r
+                    Amiga MOD compatible than ST3 to have bugs in it's MOD\r
+                   playing module.\r
+       \r
+\r
+       Question 8) Did you do something to the tunes to make them sound so\r
+                   weird?\r
+       Answer   8) No, we didn't. The PA system broke down. The left speaker\r
+                   broke and didn't play most of the middle-sounds. We are\r
+                   very sorry for this, but it's very rare that this kind of\r
+                   things happen.\r
+                   \r
+                   \r
+       Question 9) Did you use dirty little tricks in The Party II?\r
+       Answer   9) Rick Dangerous / S!P has claimed that we used the \r
+                   following dirty trick in The Party II:\r
+                        ù First telling everyone there'll be no demo from them\r
+                        ù then, all of a sudden, at the END of the compo Gore \r
+                          shouted (you know in this certain style) And Now! \r
+                          The new demo by the Future Crew.... (all other things \r
+                          were anounced like uhh.. hmm yes.. copper? by humm...  \r
+                          surbrisse..?...)\r
+                        ù and finally they turned the volume up to give the \r
+                          sound a special boost...\r
+                   \r
+                   1) We telled nobody of our demo (Panic) because we feared\r
+                      that it would scare off people. The almightyFC is gonna\r
+                      do a new demo, we can't win, so why compete? We thought\r
+                      we could this way get a lot better compo. \r
+                   \r
+                   2) Gore shouted? In fact the man who announced ALL the\r
+                       competitions (Amiga, PC and C64) was some of the Amiga-\r
+                      organizers. We didn't even know him. In fact Gore was\r
+                      nowhere near the compo room, only I and Wildfire were\r
+                      (of FC) at the compo room.\r
+                   \r
+                   3) We couldn't have boosted the volume because PA system\r
+                      was operated by two other guys. They controlled the \r
+                      volume during all the compos. Not us.\r
+                      \r
+                      \r
+       Question 10) Did FC skip parts of other groups demos?\r
+       Answer   10) Yes. Some parts were skipped because the demo run just for\r
+                    too long displaying the same effects all over again. \r
+                     If the audience began almost to die of borement because\r
+                    of looking at the same boring screen for 3-4 minutes it\r
+                    was the time to skip to next part. This could have not\r
+                    hurted the group, because people already were bored with\r
+                    the screen. Boring them more would affected the groups\r
+                    score even more.\r
+\r
+                    \r
+       Question 11) Did FC conduct a fake voting?\r
+       Answer   11) The counting of votes was an open happening. Anybody could\r
+                    have joined us to help with the counting. We invited \r
+                    everybody to join us. We made every attempt to make the\r
+                    voting as reliable as possible and it's our opinion that\r
+                    the votes were counted as correctly as possible.\r
+                    What comes to faking votes, it is a complete lie. No votes\r
+                    were forged. The results of the music competitions might\r
+                    have been surprises, but for those surprises only the\r
+                    voters can be blamed.\r
+\r
+\r
+ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿\r
+³15:                          SONIC DREAMS                                    ³\r
+ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ\r
+\r
+       \r
+       Two files which have claimed to be a demo from us under the\r
+       name of Sonic Dreams have been circulating boards around \r
+       Europe.\r
+       \r
+         These files: FCSONIC1.ZIP and FCSONIC2.ZIP\r
+         \r
+                       A*R*E  F*A*K*E*S*!\r
+                       \r
+       We don't know the maker of these files nor the purpose of them.\r
+       Under our tests we have not found any viruses nor troijans in those\r
+       files. Those files are composed of PCX pictures with some simple\r
+       C source code. Please delete the files when encountered. We \r
+        (the Future Crew) are not the makers of these files.\r
+       \r
+\r
+\r
+ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿\r
+³16:                          FINAL WORDS                                     ³\r
+ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ\r
+\r
+       This is the second revision of this file. In the first version\r
+        there was a a little "bug". The number to StarPorts' second node\r
+       was incorrect. Please, don't call that number! The number goes\r
+       to some Finnish home.\r
+       \r
+       Thank you for reading this file.\r
+       \r
+       \r
+                Signed, Abyss, GORE & Henchman / Future Crew\r
diff --git a/16/starport2/FILE_ID.DIZ b/16/starport2/FILE_ID.DIZ
new file mode 100644 (file)
index 0000000..7f4922e
--- /dev/null
@@ -0,0 +1,7 @@
+Ûßß Ûßß  Proudly Presents:\r
+Ûß  Û\r
+Û   Û The COMPLETE sources for\r
+Û   Û StarPort BBS Intro II.\r
+Û   Û\r
+Û   Û Includes updated FCINFO.TXT\r
+Û   ÛÜÜ\r
diff --git a/16/starport2/MAKE.BAT b/16/starport2/MAKE.BAT
new file mode 100644 (file)
index 0000000..dd1b086
--- /dev/null
@@ -0,0 +1,5 @@
+@echo off\r
+tasm /m9 /ml sp2.asm\r
+if ERRORLEVEL 1 goto end\r
+tlink /t /x sp2.obj\r
+:end\r
diff --git a/16/starport2/README b/16/starport2/README
new file mode 100644 (file)
index 0000000..a3756c7
--- /dev/null
@@ -0,0 +1,9 @@
+\r
+-- Starport Intro II V1.0 -- Source -- Copyright (C) 1993 Future Crew --\r
+\r
+File:          Description;\r
+SP2.COM        the second starport intro\r
+SP2.ASM        the assembler source for it\r
+MAKE.BAT       batch file to compile the demo\r
+\r
+Read the beginning of SP3.ASM for more details about the intro.\r
diff --git a/16/starport2/SP2.ASM b/16/starport2/SP2.ASM
new file mode 100644 (file)
index 0000000..1958f2c
--- /dev/null
@@ -0,0 +1,976 @@
+;--------------------------------------------------------------------\r
+;                  StarPort Intro II V1.0\r
+;--------------------------------------------------------------------\r
+;              Copyright (C) 1993 Future Crew\r
+;--------------------------------------------------------------------\r
+;                        code: Psi\r
+;                       music: Skaven\r
+;--------------------------------------------------------------------\r
+;    This code is released to the public domain. You can do\r
+;    whatever you like with this code, but remember, that if\r
+;    you are just planning on making another small intro by\r
+;    changing a few lines of code, be prepared to enter the\r
+;    worldwide lamers' club. However, if you are looking at\r
+;    this code in hope of learning something new, go right \r
+;    ahead. That's exactly why this source was released. \r
+;    (BTW: I don't claim there's anything new to find here,\r
+;    but it's always worth looking, right?) \r
+;--------------------------------------------------------------------\r
+;    The code is optimized mainly for size but also a little\r
+;    for speed. The goal was to get this little bbs intro to\r
+;    under 2K, and 1993 bytes sounded like a good size. Well,\r
+;    it wasn't easy, and there are surely places left one could \r
+;    squeeze a few extra bytes off...\r
+;      Making a small intro is not hard. Making a small intro\r
+;    with a nice feel is very hard, and you have to sacrifice\r
+;    ideas to fit the intro to the limits you have set. I had\r
+;    a lot of plans (a background piccy for example), but well,\r
+;    the size limit came first.\r
+;      I hope you enjoy my choice of size/feature ratio in this\r
+;    intro! In case you are interested, this was a three evening\r
+;    project (the last one spent testing).\r
+;--------------------------------------------------------------------\r
+;    You can compile this with TASM, but the resulting COM-file\r
+;    will be a lot larger than the released version. This is\r
+;    because all the zero data is included to the result. The\r
+;    released version was first compiled to a COM file, and then\r
+;    a separate postprocessing program was ran which removed all\r
+;    the zero data from the end of the file. If you are just \r
+;    experimenting, recompiling is as easy as MAKE.BAT. If you\r
+;    want to make this small again, you have to do some work as\r
+;    well, and make your own postprocessor.\r
+;--------------------------------------------------------------------\r
+\r
+BORDERS=0      ;set to 1 for visible border-timings\r
+\r
+code   SEGMENT para public 'CODE'\r
+       ASSUME cs:code\r
+       LOCALS\r
+       .386\r
+\r
+ORG    100h\r
+start: cld     ;filler to make the filesize exactly 1993 bytes\r
+       cld     ;filler to make the filesize exactly 1993 bytes\r
+       jmp     main\r
+\r
+;±±±±±±±±±±±±±±±± setborder ±±±±±±±±±±±±±±±±\r
+;descr: debug/change border color\r
+setborder MACRO col\r
+       IF BORDERS\r
+       push    ax\r
+       push    dx\r
+       mov     dx,3dah\r
+       in      al,dx\r
+       mov     dx,3c0h\r
+       mov     al,11h+32\r
+       out     dx,al\r
+       mov     al,col\r
+       out     dx,al\r
+       pop     dx\r
+       pop     ax\r
+       ENDIF\r
+       ENDM\r
+\r
+;ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ Simplex Adlib Player ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ\r
+;this doesn't just read raw data to output to adlib like the one\r
+;used in the last starport intro. This player really does have \r
+;note & instrument data it reads and processes!\r
+\r
+;±±±±±±±±±±±±±±±± output data to adlib ±±±±±±±±±±±±±±\r
+a_lodsboutaw03: ;size optimization related entry (instrument loading)\r
+       call    a_lodsboutaw\r
+       add     ah,3\r
+a_lodsboutaw: ;size optimization related entry (instrument loading)\r
+       lodsb\r
+a_outaw        PROC NEAR ;ah=reg,al=data\r
+       push    ax\r
+       push    cx\r
+       xchg    al,ah\r
+       mov     dx,388h\r
+       out     dx,al\r
+       mov     cx,7\r
+       call    a_wait\r
+       mov     dx,389h\r
+       mov     al,ah\r
+       out     dx,al\r
+       mov     cx,30\r
+       call    a_wait\r
+       pop     cx\r
+       pop     ax\r
+       ret\r
+a_wait:        in      al,dx\r
+       loop    a_wait\r
+       ret\r
+a_outaw        ENDP\r
+\r
+;±±±±±±±±±±±±±±±± load instrument to adlib ±±±±±±±±±±±±±±\r
+a_loadinstrument PROC NEAR\r
+       ;bx=channel, ds:si=offset to instrument data\r
+       mov     ah,ds:a_inst_table[bx]\r
+       mov     cx,4\r
+@@1:   call    a_lodsboutaw03\r
+       add     ah,20h-3\r
+       loop    @@1\r
+       add     ah,40h\r
+       call    a_lodsboutaw03\r
+       mov     ah,bl\r
+       add     ah,0c0h\r
+       jmp     a_lodsboutaw\r
+a_loadinstrument ENDP\r
+\r
+;±±±±±±±±±±±±±±±± set note on/off ±±±±±±±±±±±±±±\r
+a_playnote PROC NEAR\r
+       ;bx=channel, ax=data\r
+       push    bx\r
+       xchg    ah,bl\r
+       add     ah,0a0h\r
+       call    a_outaw\r
+       mov     al,bl\r
+       add     ah,010h\r
+       pop     bx\r
+       jmp     a_outaw\r
+a_playnote ENDP\r
+\r
+;±±±±±±±±±±±±±±±± initialize/clear/shutup adlib ±±±±±±±±±±±±±±\r
+a_init PROC NEAR\r
+       mov     ax,00120h\r
+       call    a_outaw\r
+       mov     ax,00800h\r
+       call    a_outaw\r
+       mov     ah,0bdh\r
+       call    a_outaw\r
+       mov     bp,9\r
+       xor     bx,bx\r
+       mov     di,OFFSET music_instruments\r
+@@1:   mov     si,ds:[di]\r
+       add     di,2\r
+       call    a_loadinstrument\r
+       xor     ax,ax\r
+       call    a_playnote\r
+       inc     bx\r
+       dec     bp\r
+       jnz     @@1     \r
+       ret\r
+a_init ENDP\r
+\r
+;±±±±±±±±±±±±±±±± advance music one row ±±±±±±±±±±±±±±\r
+a_dorow PROC NEAR\r
+       sub     ds:a_musiccnt,1\r
+       jnc     @@0\r
+       mov     ds:a_musiccnt,music_speed\r
+       mov     cx,music_channels\r
+       mov     di,OFFSET music_patterns\r
+       xor     bx,bx\r
+@@1:   sub     ds:a_chdelaycnt[bx],1\r
+       jns     @@2\r
+       mov     si,ds:[di]      \r
+       xor     ax,ax\r
+       call    a_playnote\r
+@@4:   lodsb   \r
+       or      al,al\r
+       jz      @@7\r
+       jns     @@6\r
+       sub     al,81h\r
+       mov     ds:a_chdelay[bx],al\r
+       lodsb\r
+@@6:   mov     dl,al\r
+       and     ax,15\r
+       mov     bp,ax\r
+       add     bp,bp\r
+       mov     ax,ds:a_note_table[bp]\r
+       shr     dl,2\r
+       and     dl,not 3\r
+       add     ah,dl\r
+       call    a_playnote\r
+       mov     al,ds:a_chdelay[bx]\r
+       mov     ds:a_chdelaycnt[bx],al\r
+       mov     ds:[di],si\r
+@@2:   add     di,4\r
+       inc     bx\r
+       loop    @@1\r
+@@0:   ret\r
+@@7:   mov     si,ds:[di+2]\r
+       jmp     @@4\r
+a_dorow ENDP\r
+\r
+;ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ Intro Routines ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ\r
+\r
+;±±±±±±±±±±±±±±±± sin/cos ±±±±±±±±±±±±±±±±\r
+;entry: ax=angle (0..65535)\r
+; exit: ax=muller (-127..127)\r
+addwcos:add    ax,ds:[bx] ;optimized entry for wavesets\r
+       mov     ds:[bx],ax\r
+cos:   add     ax,16384\r
+sin:   mov     bx,ax\r
+       mov     cx,bx\r
+       and     cx,1023\r
+       neg     cx\r
+       add     cx,1023\r
+       shr     bx,10\r
+       mov     ah,ds:sintable[bx]\r
+       xor     al,al\r
+       imul    cx\r
+       push    ax\r
+       push    dx\r
+       mov     ah,ds:sintable[bx+1]\r
+       xor     al,al\r
+       neg     cx\r
+       add     cx,1023\r
+       imul    cx\r
+       pop     bx\r
+       pop     cx\r
+       add     ax,cx\r
+       adc     dx,bx\r
+       shrd    ax,dx,11\r
+       ret\r
+\r
+;±±±±±±±±±±±±±±±± rand ±±±±±±±±±±±±±±±±\r
+;returns a random value in range -4096..4095\r
+rand   PROC NEAR\r
+       mov     eax,1107030247\r
+       mul     ds:seed\r
+       add     eax,97177\r
+       mov     ds:seed,eax\r
+       shr     eax,15\r
+       and     ax,8191\r
+       sub     ax,4096\r
+;size optimizatin, some code moved from after all rand calls\r
+       add     bx,2\r
+       mov     ds:[bx],ax\r
+       ret\r
+rand   ENDP\r
+\r
+;±±±±±±±±±±±±±±±± timer ±±±±±±±±±±±±±±±±\r
+inittimer PROC NEAR\r
+       mov     eax,fs:[8*4]\r
+       mov     ds:oldint8,eax\r
+       mov     ax,cs\r
+       shl     eax,16\r
+       mov     ax,OFFSET intti8\r
+       mov     dx,17000 ;70hz\r
+       jmp     @@1\r
+deinittimer:\r
+       mov     eax,ds:oldint8\r
+       xor     dx,dx\r
+@@1:   cli\r
+       mov     fs:[8*4],eax\r
+       mov     al,036h\r
+       out     43h,al\r
+       mov     al,dl\r
+       out     40h,al\r
+       mov     al,dh\r
+       out     40h,al\r
+       sti\r
+       ret\r
+inittimer ENDP\r
+\r
+intti8 PROC FAR ;timer interrupt\r
+       push    ax\r
+       mov     al,20h\r
+       out     20h,al\r
+       inc     cs:framecounter\r
+       pop     ax\r
+       iret\r
+intti8 ENDP\r
+\r
+;±±±±±±±±±±±±±±±± load indexed palette ±±±±±±±±±±±±±±\r
+setpal PROC NEAR\r
+       ;ds:si=pointer to colorindices\r
+       mov     dx,3c8h\r
+       xor     al,al\r
+       out     dx,al\r
+       inc     dx\r
+       mov     cx,8\r
+@@1:   xor     bh,bh\r
+       mov     bl,ds:[si]\r
+       shr     bl,2\r
+       call    setpl2\r
+       mov     bl,ds:[si]\r
+       shl     bx,2\r
+       call    setpl2\r
+       inc     si\r
+       loop    @@1\r
+       ret\r
+setpl2:        and     bx,15*2\r
+       mov     ax,word ptr ds:col0[bx]\r
+       out     dx,al\r
+       mov     al,ah\r
+       out     dx,al\r
+       mov     al,ds:col0[bx+2]\r
+       out     dx,al\r
+       ret\r
+setpal ENDP\r
+\r
+;±±±±±±±±±±±±±± clear & copy videobuffer to screen ±±±±±±±±±±±±±±\r
+clearcopy PROC NEAR\r
+;---copy/clear buf\r
+       xor     edx,edx\r
+       mov     si,OFFSET vbuf\r
+       mov     bx,4\r
+       mov     cx,200\r
+       mov     di,-4\r
+@@1:   mov     bp,5\r
+@@2:   REPT    2\r
+       mov     eax,ds:[si]\r
+       add     di,bx\r
+       mov     ds:[si],edx\r
+       add     si,bx\r
+       mov     es:[di],eax\r
+       ENDM\r
+       dec     bp\r
+       jnz     @@2\r
+       add     si,bx\r
+       dec     cx\r
+       jnz     @@1\r
+       ret\r
+clearcopy ENDP\r
+\r
+;±±±±±±±±±±±±±± draw a small pixel ±±±±±±±±±±±±±±\r
+pset1  PROC NEAR ;ds:di=destination center, si=xmask offset\r
+       mov     al,ds:colb[si]\r
+       or      ds:[di],al\r
+@@1:   ret\r
+pset1  ENDP\r
+\r
+;±±±±±±±±±±±±±± draw a big pixel (depending on Z) ±±±±±±±±±±±±±\r
+pset2  PROC NEAR ;ds:di=destination center, si=xmask offset\r
+       mov     ax,ds:colbww[si]\r
+       or      ds:[di+0],ax\r
+       or      ds:[di+44],ax\r
+       cmp     bp,8300 ;zcompare for size\r
+       jl      pset3\r
+       ;smaller one\r
+       mov     ax,ds:colbw[si]\r
+       or      ds:[di-44],ax\r
+       or      ds:[di+88],ax\r
+       mov     ax,ds:colbv[si]\r
+       or      ds:[di-88],ax\r
+       or      ds:[di+132],ax\r
+       ret\r
+pset3: ;larger one\r
+       or      ds:[di-44],ax\r
+       or      ds:[di+88],ax\r
+       mov     ax,ds:colbw[si]\r
+       or      ds:[di-88],ax\r
+       or      ds:[di+132],ax\r
+       ret\r
+pset2  ENDP\r
+\r
+;±±±±±±±±±±±±±± add a letter composed of big dots to dotlist ±±±±±±±±±±±±±\r
+letter3d PROC NEAR\r
+       ;bx=letter\r
+       ;si=basex\r
+       ;bp=basey\r
+       sub     bx,'A'\r
+       jc      @@0\r
+       shl     bx,3\r
+       mov     di,ds:nextdot\r
+       mov     cx,8\r
+@@1:   push    cx\r
+       push    si\r
+       mov     cx,8\r
+@@2:   cmp     ds:font[bx],0\r
+       je      @@3\r
+       mov     ds:dots[di],si\r
+       mov     ds:dots[di+2],bp\r
+       ;zsinus\r
+       push    si\r
+       add     si,ds:sinus1\r
+       sar     si,6\r
+       and     si,63\r
+       mov     al,ds:sintable[si]\r
+       cbw\r
+       pop     si\r
+       shl     ax,2\r
+       mov     ds:dots[di+4],ax\r
+       ;\r
+       mov     word ptr ds:dots[di+6],OFFSET pset2\r
+       add     di,8\r
+       and     di,DOTNUM1*8-1\r
+@@3:   inc     bx\r
+       add     si,LETTERDOTSPACING\r
+       loop    @@2\r
+       pop     si\r
+       add     bx,320-8\r
+       add     bp,LETTERDOTSPACING\r
+       pop     cx\r
+       loop    @@1\r
+       mov     ds:nextdot,di\r
+@@0:   ret\r
+letter3d ENDP\r
+\r
+;±±±±±±±±±±±±±± calc 2x2 rotation matrix ±±±±±±±±±±±±±\r
+set3drot PROC NEAR\r
+       ;ax=angle,ds:di=pointer to matrix\r
+       push    ax\r
+       call    sin\r
+       mov     ds:[di+r01-r00],ax\r
+       neg     ax\r
+       mov     ds:[di+r10-r00],ax\r
+       pop     ax\r
+       call    cos\r
+       mov     ds:[di+r00-r00],ax\r
+       mov     ds:[di+r11-r00],ax\r
+       ret\r
+set3drot ENDP\r
+\r
+;±±±±±±±±±±±± rotate point with 2x2 rotation matrix (innerpart) ±±±±±±±±±±±±±\r
+rotate2x2i PROC NEAR\r
+       ;(di,bp)->(cx) with matrix half at ds:si\r
+       ;this is the inner part, called twice\r
+       push    bx\r
+       mov     ax,di\r
+       imul    word ptr ds:[si]\r
+       mov     cx,ax\r
+       mov     bx,dx\r
+       mov     ax,bp\r
+       imul    word ptr ds:[si+2]\r
+       add     cx,ax\r
+       adc     bx,dx\r
+       shrd    cx,bx,14\r
+       pop     bx\r
+       add     si,4\r
+       ret\r
+rotate2x2i ENDP\r
+\r
+;±±±±±±±±±±±±±± advance demo one frame (raw work) ±±±±±±±±±±±±±\r
+doit   PROC NEAR\r
+;======wait for border\r
+       setborder 0\r
+       mov     dx,3dah\r
+@@w1:  in      al,dx\r
+       test    al,8\r
+       jnz     @@w1\r
+@@w2:  in      al,dx\r
+       test    al,8\r
+       jz      @@w2\r
+       setborder 30\r
+;======done\r
+       mov     si,ds:index\r
+       push    si\r
+       call    setpal\r
+       pop     si\r
+       add     si,9\r
+       cmp     si,OFFSET index4\r
+       jbe     @@i2\r
+       mov     si,OFFSET index1\r
+@@i2:  mov     ds:index,si\r
+       mov     al,2\r
+       mov     ah,ds:[si+8]\r
+       mov     dx,3c4h\r
+       out     dx,ax\r
+       call    clearcopy\r
+;======do timer simulation stuff\r
+       setborder 28\r
+       xor     cx,cx\r
+       mov     ds:scrollsubber,0\r
+       xchg    cx,ds:framecounter\r
+       jcxz    @@78\r
+@@77:  push    cx\r
+       add     ds:scrollsubber,SCROLLSPEED\r
+       call    doit70\r
+       pop     cx\r
+       loop    @@77\r
+       setborder 26\r
+@@78:;======\r
+;---redraw dots\r
+       mov     cx,DOTNUM\r
+       mov     bx,OFFSET dots\r
+@@1:   push    cx\r
+       push    bx\r
+       mov     bp,ds:[bx+2]\r
+       mov     di,ds:[bx+4]\r
+       cmp     word ptr ds:[bx+6],OFFSET pset2\r
+       jne     @@5\r
+       ;ysinus\r
+       mov     cx,ds:[bx]\r
+       mov     si,ds:sinus2\r
+       add     si,cx\r
+       sar     si,7\r
+       and     si,63\r
+       mov     al,ds:sintable[si]\r
+       cbw\r
+       shl     ax,2\r
+       add     bp,ax\r
+       ;scroll\r
+       sub     cx,ds:scrollsubber\r
+       mov     ds:[bx],cx\r
+       cmp     cx,-3900\r
+       jl      @@7\r
+       cmp     cx,3900\r
+       jg      @@7\r
+@@5:   ;--rotate coordinates\r
+       mov     si,OFFSET r00\r
+       call    rotate2x2i\r
+       push    cx\r
+       call    rotate2x2i\r
+       pop     di\r
+       mov     bp,ds:[bx]\r
+       mov     si,OFFSET p00\r
+       push    cx\r
+       call    rotate2x2i\r
+       push    cx\r
+       call    rotate2x2i\r
+       pop     bp\r
+       pop     di\r
+       ;bp=Z, cx=X, di=Y\r
+       add     bp,ds:zadder\r
+       cmp     bp,1024\r
+       jl      @@7\r
+       ;--project\r
+       mov     ax,256\r
+       imul    di\r
+       idiv    bp\r
+       add     ax,100\r
+       mov     di,ax\r
+       mov     ax,307\r
+       imul    cx\r
+       idiv    bp\r
+       add     ax,160\r
+       mov     si,ax\r
+       ;si=SX, di=SY\r
+       mov     ax,ds:[bx+6]\r
+       cmp     si,319\r
+       ja      @@7\r
+       cmp     di,199\r
+       ja      @@7\r
+       ;calc dest address & xmask offset\r
+       add     di,di\r
+       mov     di,ds:rows[di]\r
+       add     si,si\r
+       add     di,ds:cols[si]\r
+       ;\r
+       call    ax\r
+@@7:   pop     bx\r
+       pop     cx\r
+       add     bx,8\r
+       dec     cx\r
+       jnz     @@1\r
+       ret\r
+doit   ENDP\r
+\r
+;±±±±±±±±±±±±±± advance demo counters 1/70 sec ±±±±±±±±±±±±±\r
+;a separate routine is used to get frame syncronization for\r
+;slower machines (and slow vga cards)\r
+doit70 PROC NEAR\r
+;---add sinuses & udforce\r
+       add     ds:sinus1,70\r
+       add     ds:sinus2,177\r
+       add     ds:udforced,3000\r
+;---set wave1\r
+       mov     bx,OFFSET wwave\r
+       mov     ax,77\r
+       call    addwcos\r
+       sar     ax,5\r
+       mov     ds:wave1,ax\r
+;---set zadder\r
+       mov     bx,OFFSET zwave\r
+       mov     ax,370\r
+       call    addwcos\r
+       sar     ax,3\r
+       add     ax,8888\r
+       mov     ds:zadder,ax\r
+;---set 3d rotate YZ\r
+       mov     bx,OFFSET udwave\r
+       mov     ax,ds:wave1\r
+       call    addwcos\r
+       imul    ds:udforce\r
+       shrd    ax,dx,8\r
+       mov     di,OFFSET r00\r
+       call    set3drot\r
+;---set 3d rotate XZ\r
+       mov     bx,OFFSET lrwave\r
+       mov     ax,200\r
+       call    addwcos\r
+       sar     ax,1\r
+       mov     di,OFFSET p00\r
+       call    set3drot\r
+;---add more text to 3d scroller\r
+       sub     ds:textcnt,SCROLLSPEED\r
+       jnc     @@t1\r
+       mov     ds:textcnt,LETTERDOTSPACING*8-1\r
+       mov     si,ds:text\r
+       mov     bl,ds:[si]\r
+       IFDEF XORTEXTS\r
+       xor     bl,17h\r
+       ENDIF\r
+       and     bx,255\r
+       jz      @@t3\r
+       inc     si\r
+       mov     ds:text,si\r
+       cmp     bl,32\r
+       jge     @@t4\r
+       shl     bx,SCROLLDELAYSHL\r
+       mov     ds:textcnt,bx\r
+       jmp     @@t1\r
+@@t4:  mov     bp,0\r
+       mov     si,4100\r
+       call    letter3d\r
+       jmp     @@t1\r
+@@t3:  mov     si,OFFSET text0\r
+       mov     ds:text,si\r
+@@t1:  ;;;\r
+;======adlib music\r
+       jmp     a_dorow\r
+doit70 ENDP\r
+\r
+;ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ Main routine ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ\r
+;stack @ cs:0fffeh\r
+\r
+main   PROC NEAR\r
+;ÍÍÍÍÍÍÍÍÍ Zero Zerodata & Init Segs ÍÍÍÍÍÍÍ\r
+.8086  ;;;\r
+       push    cs\r
+       push    cs\r
+       pop     ds\r
+       pop     es\r
+       mov     cx,(zeroend-zerobeg)/2\r
+       mov     di,OFFSET zerobeg\r
+       xor     ax,ax ;zero used later\r
+       rep     stosw\r
+       mov     dx,0a000h\r
+       mov     es,dx\r
+;segments now set: DS=code/data ES=vram\r
+;ÍÍÍÍÍÍÍÍÍ Check for 386 ÍÍÍÍÍÍÍÍÍ\r
+       push    sp\r
+       pop     dx\r
+       cmp     dx,sp\r
+       jz      @@o1\r
+@@o2:  jmp     endansi ;80(1)86\r
+.286p  ;;;\r
+@@o1:  mov     bx,OFFSET rows\r
+       sgdt    ds:[bx]\r
+       cmp     byte ptr ds:[bx+5],0\r
+       js      @@o2\r
+;ÍÍÍÍÍÍÍÍÍ Check for VGA ÍÍÍÍÍÍÍÍÍ\r
+.386p  ;;;\r
+       mov     fs,ax ;ax was zero\r
+;segments now set: DS=code/data ES=vram FS=zeropage\r
+       mov     ax,1a00h\r
+       int     10h\r
+       cmp     al,01ah\r
+       jne     endansi ;no vga\r
+       cmp     bl,7\r
+       jb      endansi ;no vga\r
+;ÍÍÍÍÍÍÍÍÍ Initialize - doinit 0 ÍÍÍÍÍÍÍÍÍ\r
+       ;copy vga font to font buffer\r
+       mov     ax,13h\r
+       int     10h\r
+       mov     cx,'Z'-'A'+1\r
+       mov     bx,16\r
+       mov     ax,'A'+0eh*256\r
+@@a1:  int     10h\r
+       inc     al\r
+       loop    @@a1\r
+       mov     cx,8*320/2\r
+       mov     bx,OFFSET font\r
+       xor     di,di\r
+@@a2:  mov     ax,es:[di]\r
+       mov     ds:[di+bx],ax\r
+       add     di,2\r
+       loop    @@a2\r
+;ÍÍÍÍÍÍÍÍÍ Initialize - vga ÍÍÍÍÍÍÍÍÍ\r
+       ;init videomode 320x200x16\r
+       mov     ax,0dh\r
+       int     10h\r
+       ;set up rows/cols/etc\r
+       mov     si,-2\r
+       mov     di,OFFSET vbuf-44\r
+       mov     bl,128\r
+       xor     bp,bp\r
+       jmp     @@b5\r
+@@b1:  mov     ds:rows[si],di\r
+       mov     ds:colb[si],bl\r
+       mov     ds:colbww[si],cx\r
+       shr     cl,1\r
+       rcr     ch,1\r
+       mov     ds:colbw[si],dx\r
+       shr     dl,1\r
+       rcr     dh,1\r
+       mov     ds:colbv[si],ax\r
+       shr     al,1\r
+       rcr     ah,1\r
+       mov     ds:cols[si],bp\r
+       ror     bl,1\r
+       jnc     @@b4\r
+       inc     bp\r
+@@b5:  mov     cx,0000000011111110b\r
+       mov     dx,0000000001111100b\r
+       mov     ax,0000000000111000b\r
+@@b4:  add     di,44\r
+       add     si,2\r
+       cmp     si,(320)*2\r
+       jle     @@b1\r
+       ;set simplex palette order (16 color mode)\r
+       mov     dx,3dah\r
+       in      al,dx\r
+       mov     dl,0c0h\r
+       xor     ax,ax\r
+       mov     cx,16\r
+@@b2:  out     dx,al\r
+       out     dx,al\r
+       inc     al\r
+       loop    @@b2\r
+       mov     al,20h\r
+       out     dx,al\r
+;ÍÍÍÍÍÍÍÍÍ Initialize - doinit ÍÍÍÍÍÍÍÍÍ\r
+       mov     cx,DOTNUM\r
+       mov     bx,OFFSET dots-2\r
+@@c1:  push    cx\r
+       call    rand\r
+       call    rand\r
+       call    rand\r
+       sar     ax,2\r
+       mov     ds:[bx],ax\r
+       add     bx,2\r
+       mov     word ptr ds:[bx],OFFSET pset1\r
+       pop     cx\r
+       loop    @@c1\r
+;ÍÍÍÍÍÍÍÍÍ Initialize - others ÍÍÍÍÍÍÍÍÍ\r
+       call    a_init\r
+       call    inittimer\r
+;ÍÍÍÍÍÍÍÍÍ Do the intro stuff ÍÍÍÍÍÍÍÍÍ\r
+again: call    doit\r
+       mov     ah,1\r
+       int     16h\r
+       jz      again\r
+       mov     ah,0\r
+       int     16h\r
+;ÍÍÍÍÍÍÍÍÍ DeInitialize ÍÍÍÍÍÍÍÍÍ\r
+       call    deinittimer\r
+       call    a_init ;reinitializing adlib shuts it up\r
+;ÍÍÍÍÍÍÍÍÍ Display end ansi (only thing done if no 386 or vga) ÍÍÍÍÍÍÍÍÍ\r
+endansi:mov    ax,3h\r
+       int     10h\r
+       mov     si,OFFSET endtext\r
+       push    0b800h  ;if the user has an MGA or HGC\r
+       pop     es      ;it's not my problem :-)\r
+       xor     di,di\r
+       mov     ah,0eh\r
+@@1:   lodsb\r
+       IFDEF XORTEXTS\r
+       xor     al,17h\r
+       ENDIF\r
+       cmp     al,31\r
+       jae     @@2\r
+       mov     ah,al\r
+       jmp     @@1\r
+@@2:   jz      @@3\r
+       stosw\r
+       jmp     @@1\r
+@@3:   mov     ax,4c00h\r
+       int     21h\r
+main   ENDP\r
+       \r
+;ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ Initialized (nonzero) data ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ\r
+\r
+;pointer & delay counter for scrolltext\r
+text   dw      OFFSET text0\r
+textcnt        dw      1\r
+\r
+;3d rotation values (more in zerodata)\r
+udforced LABEL DWORD\r
+       dw      0\r
+udforce        dw      64\r
+lrwave dw      -20000\r
+zwave  dw      16000\r
+\r
+sintable LABEL BYTE ;sine table (circle is 64 units)\r
+db 0,12,24,36,48,59,70,80,89,98,105,112,117,121,124,126,127,126\r
+db 124,121,117,112,105,98,89,80,70,59,48,36,24,12,0,-12,-24,-36\r
+db -48,-59,-70,-80,-89,-98,-105,-112,-117,-121,-124,-126,-127\r
+db -126,-124,-121,-117,-112,-105,-98,-89,-80,-70,-59,-48,-36\r
+db -24,-12,0,3,6,9,12,15,18,21,24,27,30,33,36,39,42,45,48,51,54\r
+db 57,59,62,65,67,70\r
+\r
+;adlib player data\r
+a_inst_table LABEL BYTE\r
+       db 20h+0,20h+1,20h+2,20h+8,20h+9,20h+10,20h+16,20h+17,20h+18\r
+NTB equ 8192 ;+1024*1\r
+a_note_table LABEL WORD\r
+       dw NTB+363,NTB+385,NTB+408,NTB+432,NTB+458,NTB+485\r
+       dw NTB+514,NTB+544,NTB+577,NTB+611,NTB+647,NTB+868\r
+       ;note: a zero word is expected after this table (found in col0)\r
+       \r
+col0   db       0, 0, 0 ,0     ;background color\r
+col1   db       0,15,35 ,0     ;delay color 3\r
+col2   db      16,30,48 ,0     ;delay color 2\r
+col3   db      32,45,55 ,0     ;delay color 1\r
+col4   db      60,61,62        ;brightest color\r
+       ;1      . x . x . x . x . x . x . x . x\r
+       ;2      . . x x . . x x . . x x . . x x\r
+       ;4      . . . . x x x x . . . . x x x x\r
+       ;8      . . . . . . . . x x x x x x x x\r
+;palette indices for 4 palettes. Last number is bitplane to write\r
+;during the frame having this palette\r
+index1 db      04h,34h,24h,34h,14h,34h,24h,34h ,1 ;1248\r
+index2 db      03h,23h,13h,23h,44h,44h,44h,44h ,8 ;8124\r
+index3 db      02h,12h,44h,44h,33h,33h,44h,44h ,4 ;4812\r
+index4 db      01h,44h,33h,44h,22h,44h,33h,44h ,2 ;2481\r
+index  dw      OFFSET index1 ;offset to current index\r
+\r
+;################## Music - (tune by skaven/fc) ###################\r
+;generated with ST3->SIMPLEXADLIB, handoptimized by psi (283 bytes)\r
+music_channels equ 8\r
+music_speed equ 8\r
+music_instruments LABEL BYTE\r
+dw OFFSET ains6\r
+dw OFFSET ains2\r
+dw OFFSET ains4\r
+dw OFFSET ains3\r
+dw OFFSET ains3\r
+dw OFFSET ains1\r
+dw OFFSET ains1\r
+dw OFFSET ains4\r
+ains1 LABEL BYTE\r
+db 65,194,6,0,35,242,240,240,1,0,4\r
+ains2 LABEL BYTE\r
+db 145,64,135,128,243,111,35,3,1,1,2\r
+ains3 LABEL BYTE\r
+db 225,33,17,128,17,19,34,34,0,0,12\r
+ains4 LABEL BYTE\r
+db 97,33,27,0,98,132,86,85,0,0,14\r
+ains6 LABEL BYTE\r
+db 145,64,135,136,243,111,35,3,1,1,2\r
+music_patterns LABEL BYTE\r
+ach0 dw OFFSET ach0d,OFFSET ach0dr\r
+ach1 dw OFFSET ach1d,OFFSET ach1dr\r
+ach2 dw OFFSET ach2d,OFFSET ach2dr\r
+ach3 dw OFFSET ach3d,OFFSET ach3d\r
+ach4 dw OFFSET ach4d,OFFSET ach4d\r
+ach5 dw OFFSET ach5d,OFFSET ach5d\r
+ach6 dw OFFSET ach6d,OFFSET ach6d\r
+ach7 dw OFFSET ach7d,OFFSET ach7d\r
+ach0d LABEL BYTE\r
+db 081h\r
+ach0dr LABEL BYTE\r
+db 057h,050h,050h,055h,057h,050h,055h,057h\r
+db 050h,055h,057h,050h,055h,057h,050h,055h\r
+db 0\r
+ach1d LABEL BYTE\r
+db 081h\r
+ach1dr LABEL BYTE\r
+db 050h,055h,057h,050h,055h,057h,050h,055h\r
+db 057h,050h,055h,057h,050h,055h,057h,050h\r
+db 0\r
+ach2d LABEL BYTE\r
+db 0C0h,050h,084h\r
+db 030h,020h,030h,020h,02Ah,01Ah,02Ah,01Ah\r
+db 030h,020h,030h,020h,02Ah,01Ah,02Ah,01Ah\r
+ach2dr LABEL BYTE\r
+db 030h,020h,030h,020h,02Ah,01Ah,02Ah,01Ah\r
+db 025h,015h,025h,015h,028h,018h,02Ah,01Ah\r
+db 0\r
+ach3d LABEL BYTE\r
+db 0A0h,050h,040h,0C0h,040h,088h,040h,040h\r
+db 03Ah,042h,090h,045h,088h,040h,042h,040h\r
+db 047h,090h,04Ah,088h,045h,098h,040h\r
+db 0\r
+ach4d LABEL BYTE\r
+db 0A0h,050h,030h,0C0h,047h,088h,047h,043h\r
+db 042h,045h,047h,045h,048h,047h,047h,050h\r
+db 052h,084h,050h,04Ah,088h,050h,098h,045h\r
+db 0\r
+ach5d LABEL BYTE\r
+db 0C0h,020h,0A0h,010h,010h,090h,010h,02Ah\r
+db 025h,088h,028h,02Ah,090h,010h,02Ah,025h\r
+db 088h,028h,02Ah\r
+db 0\r
+ach6d LABEL BYTE\r
+db 0C0h,020h,0A0h,020h,020h,090h,020h,01Ah\r
+db 015h,088h,018h,01Ah,090h,020h,01Ah,015h\r
+db 088h,018h,01Ah\r
+db 0\r
+ach7d LABEL BYTE\r
+db 0C0h,00Ch,0FEh,050h,090h,00Ch,081h,04Ah\r
+db 050h,084h,052h,055h,086h,04Ah,081h,050h\r
+db 04Ah,086h,050h,082h,055h,098h,045h\r
+db 0\r
+;#########################################################\r
+\r
+SCROLLSPEED equ        90\r
+SCROLLDELAYSHL equ 9\r
+LETTERDOTSPACING equ 128\r
+\r
+db 0fch\r
+\r
+text0  LABEL BYTE ;scrolltext (numbers are delays)\r
+       db      31,25,'CALL STARPORT',9,'FUTURE CREW WORLD HQ',9,'CDN',9,'GRAVIS EURO',9,'AND MORE',0\r
+\r
+endtext        LABEL BYTE ;endansi... well... endansiline (numbers are colors)\r
+       db      15\r
+       db      'StarPort'\r
+       db      3,' ÄÄ ',11\r
+       db      'V32bis +358-0-8044626'\r
+       db      ' +358-0-8041133'\r
+       db      3,' ÄÄ ',15\r
+       db      'FC-WHQ'\r
+       db      31\r
+endtext1 LABEL BYTE\r
+\r
+db 0fch\r
+\r
+;ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ Uninitialized (zero) data ÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛÛ\r
+\r
+zerobeg        LABEL WORD ;start zero clear from here\r
+\r
+rows   dw      320 dup(0)      ;offsets to screen rows\r
+cols   dw      320 dup(0)      ;offsets to screen cols\r
+colb   db      320 dup(0,0)    ;bitmasks for screen cols\r
+colbv  dw      320 dup(0)      ;wide -"-\r
+colbw  dw      320 dup(0)      ;wider -"-\r
+colbww dw      320 dup(0)      ;very wide -"-\r
+\r
+ALIGN 4\r
+       db      44*8 dup(0) ;negative overflow for videobuffer\r
+vbuf   LABEL BYTE\r
+       db      44*200 dup(0) ;video buffer\r
+       db      44*8 dup(0) ;positive overflow for videobuffer\r
+\r
+ALIGN 4\r
+font   LABEL BYTE\r
+       db      8 dup(320 dup(0)) ;font buffer\r
+\r
+\r
+DOTNUM1        equ     256     ;number of dots used for text\r
+DOTNUM equ     444     ;total number of dots\r
+ALIGN 4\r
+dots   LABEL WORD\r
+       dw      DOTNUM dup(0,0,0,0) ;x,y,z,routine data for each dot\r
+       \r
+;2x2 rotation matrices\r
+r00    dw      0\r
+r10    dw      0\r
+r01    dw      0\r
+r11    dw      0\r
+p00    dw      0\r
+p10    dw      0\r
+p01    dw      0\r
+p11    dw      0\r
+\r
+;zero initialized 3d rotation stuff\r
+zadder dw      0\r
+wave1  dw      0\r
+udwave dw      0\r
+wwave  dw      0\r
+sinus1 dw      0\r
+sinus2 dw      0\r
+\r
+;adlib data\r
+a_musiccnt dw  0\r
+a_chdelaycnt db        9 dup(0)\r
+a_chdelay db   9 dup(0)\r
+ALIGN 2\r
+\r
+;misc\r
+nextdot        dw      0\r
+scrollsubber dw        0\r
+framecounter dw        0\r
+oldint8        dd      0\r
+seed   dd      0\r
+\r
+padder db      16 dup(0)\r
+zeroend        LABEL WORD ;end zero clear here\r
+\r
+code   ENDS\r
+       END start\r
diff --git a/16/starport2/SP2.COM b/16/starport2/SP2.COM
new file mode 100644 (file)
index 0000000..0234054
Binary files /dev/null and b/16/starport2/SP2.COM differ
index a453f15..51d05af 100644 (file)
@@ -1,67 +1,76 @@
-c2e.convert_special: 0
-e2c.convert_num: 0
-openfiles: /dos/z/16/src/scroll.c:0:378:1:
-snr_recursion_level: 0
-convertcolumn_horizontally: 0
-adv_open_matchname: 0
-show_mbhl: 1
-view_line_numbers: 1
-fb_show_backup_f: 0
-htmlbar_thumbnailwidth: 300
-view_left_panel: 0
-default_mime_type: text/plain
-e2c.convert_xml: 1
-c2e.convert_iso: 0
-opendir: file:///dos/z/16/src
-wrap_text_default: 0
-bookmarks_filename_mode: 1
-snr_casesens: 0
-view_blocks: 1
-name: Project 16
-fb_show_hidden_f: 0
-editor_tab_width: 4
-template: /usr/share/bluefish/templates/C_header_GPL
-show_visible_spacing: 0
-view_statusbar: 1
-display_right_margin: 1
-c2e.IE_apos_workaround: 0
-outputb_scroll_mode: 0
-leftpanel_active_tab: 0
-enable_syntax_scan: 1
-ssearch_regex: 0
-e2c.convert_iso: 0
-ssearch_casesens: 0
-charmap_block: 1
-snr_replacetype: 0
-savedir: file:///dos/z/16/src
-spell_check_default: 1
-spell_insert_entities: 0
-last_filefilter: 
-htmlbar_notebooktab: 0
-view_blockstack: 1
-snr_escape_chars: 0
-htmlbar_view: 1
-spell_lang: en
-ssearch_dotmatchall: 0
-autocomplete: 1
-outputb_show_all_output: 0
-bookmarks_show_mode: 0
-snippets_show_as_menu: 1
+view_main_toolbar: 1
+bmarksearchmode: 0
+snr_scope: 2
+snr_type: 0
+view_cline: 0
+editor_indent_wspaces: 0
+c2e.convert_xml: 1
+snr_dotmatchall: 0
+c2e.convert_symbol: 0
+filegloblist: *.xml
+filegloblist: *.txt
+filegloblist: *.shtml
+filegloblist: *.py
+filegloblist: *.pl
+filegloblist: *.php
+filegloblist: *.js
+filegloblist: *.java
+filegloblist: *.htm
+filegloblist: *.html
+filegloblist: *.h
+filegloblist: *.css
+filegloblist: *.cpp
+filegloblist: *.cgi
+filegloblist: *.c
+fb_focus_follow: 1
 adv_open_recursive: 0
+recent_dirs: file:///dos/z/16/src
 encoding: SHIFT_JIS
-e2c.convert_special: 0
 autoindent: 1
+e2c.convert_special: 0
 fb_viewmode: 0
-recent_dirs: file:///dos/z/16/src
-fb_focus_follow: 1
-ssearch_unescape: 0
-c2e.convert_symbol: 0
-snr_dotmatchall: 0
-c2e.convert_xml: 1
-editor_indent_wspaces: 0
-view_cline: 0
-snr_type: 0
-snr_scope: 2
-bmarksearchmode: 0
-view_main_toolbar: 1
+htmlbar_view: 1
+snippets_show_as_menu: 1
+bookmarks_show_mode: 0
+outputb_show_all_output: 0
+autocomplete: 1
+spell_lang: en
+snr_escape_chars: 0
+view_blockstack: 1
+htmlbar_notebooktab: 0
+last_filefilter: 
+spell_insert_entities: 0
+spell_check_default: 1
+savedir: file:///dos/z/16/src
+snr_replacetype: 0
+charmap_block: 1
+e2c.convert_iso: 0
+enable_syntax_scan: 1
+outputb_scroll_mode: 0
+leftpanel_active_tab: 0
+c2e.IE_apos_workaround: 0
+display_right_margin: 1
+template: /usr/share/bluefish/templates/C_header_GPL
+editor_tab_width: 4
+view_statusbar: 1
+fb_show_hidden_f: 0
+name: Project 16
+view_blocks: 1
+snr_casesens: 0
+c2e.convert_iso: 0
+bookmarks_filename_mode: 1
+wrap_text_default: 0
+opendir: file:///dos/z/16/src
+e2c.convert_xml: 1
+default_mime_type: text/plain
+view_left_panel: 0
+htmlbar_thumbnailwidth: 300
+fb_show_backup_f: 0
+view_line_numbers: 1
+show_mbhl: 1
+adv_open_matchname: 0
+convertcolumn_horizontally: 0
+snr_recursion_level: 0
 e2c.convert_symbol: 0
+e2c.convert_num: 0
+c2e.convert_special: 0
diff --git a/SCROLL.SMP b/SCROLL.SMP
deleted file mode 100644 (file)
index 398f578..0000000
Binary files a/SCROLL.SMP and /dev/null differ
diff --git a/doc/CGUIDE_3.TXT b/doc/CGUIDE_3.TXT
new file mode 100644 (file)
index 0000000..23f6a6b
--- /dev/null
@@ -0,0 +1,14499 @@
+\r
+                                    \r
+                   THE IBM PC PROGRAMMER'S GUIDE TO C\r
+                                    \r
+                                    \r
+                                    \r
+                               3rd Edition\r
+                                    \r
+                                    \r
+                                    \r
+                             Matthew Probert\r
+\r
+\r
+                                    \r
+                            COPYRIGHT NOTICE\r
+\r
+\r
+This publication remains the property of Matthew Probert. License is\r
+hereby given for this work to be freely distibuted in whole under the\r
+proviso that credit is given to the author. Sections of this work may be\r
+used and distributed without payment under the proviso that credit is\r
+given to both this work and the author. Source code occuring in this work\r
+may be used within commercial and non-commercial applications without\r
+charge and without reference to the author.\r
+\r
+ BIOGRAPHICAL NOTES\r
+\r
+\r
+Matthew Probert is a software consultant working for his own firm,\r
+Servile Software. He has been involved with micro-computer software\r
+design and programming since the age of eighteen and has been involved\r
+with the C programming language for the past ten years.\r
+\r
+His educational background lies in the non-too distinguished honour of\r
+having a nervous break down during his last year of secondary school\r
+which resulted in a lack of formal qualifications. However, Matthew has\r
+made up for it by achieving a complete recovery and has been studying\r
+Psychology with particular attention to behaviourism and conversation\r
+ever since.\r
+\r
+His chequered career spans back twelve years during which time he has\r
+trained people in the design and use of database management applications,\r
+been called upon to design and implement structured methodologies and has\r
+been a "good old fashioned" analyst programmer.\r
+\r
+Matthew Probert is currently researching Artificial Intelligence with\r
+particular reference to the application of natural language processing,\r
+whereby a computer software package may decode written human language and\r
+respond to it in an intelligent manner. He is also monitoring the\r
+progress of facilitated communication amongst autistic and children with\r
+severe learning and challenging behaviour and hopes one day to be able to\r
+develope a computer based mechanism for true and reliable communication\r
+between autistic people and the rest of society.\r
+\r
+\r
+Matthew Probert can be contacted via\r
+     Servile Software\r
+     5 Longcroft Close\r
+     Basingstoke\r
+     Hampshire\r
+     RG21 8XG\r
+     England\r
+\r
+     Telephone 01256 478576\r
+\r
+                                    \r
+                                 PREFACE\r
+\r
+In 1992, an English software house, Servile Software published a paper\r
+entitled "HOW TO C", which sought to introduce computer programmers to\r
+the C programming language. That paper was written by Matthew Probert. A\r
+follow up effort was "HOW TO CMORE", a document that was also published\r
+by Servile Software. Now those two documents have been amalgamated and\r
+thoroughly revamped to create this book. I have included loads of new\r
+source code that can be lifted directly out of the text.\r
+\r
+All the program listings have been typed in to the Turbo C compiler,\r
+compiled and executed successfully before being imported into this\r
+document.\r
+\r
+I hope you enjoy my work, and more I hope that you learn to program in C.\r
+It really is a great language, there can be no other language that gives\r
+the computer the opportunity to live up to the old saying;\r
+\r
+\r
+"To err is human, to make a complete balls up requires a computer!"\r
+\r
+\r
+\r
+Warning!\r
+\r
+This document is the result of over ten years experience as a software\r
+engineer. This document contains professional source code that is not\r
+intended for beginers.\r
+\r
+\r
+                                    \r
+                              INTRODUCTION\r
+\r
+\r
+The major distinguishing features of the C programming language are;\r
+\r
+ ú    block-structured flow-control constructs (typical of most high-level\r
+      languages);\r
+ ú    freedom to manipulate basic machine objects (eg: bytes) and to refer\r
+      to them using any particular object view desired (typical of assembly-\r
+      languages);\r
+ ú    both high-level operations (eg: floating-point arithmetic) and low-\r
+      level operations (which map closely onto machine-language instructions,\r
+      thereby offering the means to code in an optimal, yet portable, manner).\r
+\r
+This book sets out to describe the C programming language, as commonly\r
+found with compilers for the IBM PC, to enable a computer programmer with\r
+no previous knowledge of the C programming language to program in C using\r
+the IBM PC including the ROM facilities provided by the PC and facilities\r
+provided DOS.\r
+\r
+It is assumed that the reader has access to a C compiler, and to the\r
+documentation that accompanies it regarding library functions.\r
+\r
+The example programs were written with Borland's Turbo C, most of the non-\r
+standard facilities provided by Turbo C should be found in later releases\r
+of Microsoft C.\r
+\r
+\r
+\r
+Differences Between the Various Versions of C\r
+\r
+The original C (prior to the definitive book by K&R) defined the\r
+combination assignment operators (eg: +=, *=, etc.) backwards (ie: they\r
+were written =+, =*, etc.).  This caused terrible confusion when a\r
+statement such as\r
+\r
+ x=-y;\r
+was compiled - it could have meant\r
+\r
+ x = x - y;\r
+or\r
+\r
+ x = (-y);\r
+Ritchie soon spotted this ambiguity and changed the language to have\r
+these operators written in the now-familiar manner (+=, *=, etc.).\r
+\r
+The major variations, however, are between K&R C and ANSI C.  These can\r
+be summarized as follows:\r
+\r
+ ú    introduction of function prototypes in declarations; change of\r
+      function definition preamble to match the style of prototypes;\r
+ ú    introduction of the ellipsis ("...") to show variable-length\r
+      function argument lists;\r
+ ú    introduction of the keyword `void' (for functions not returning a\r
+      value) and the type `void *' for generic pointer variables;\r
+ ú    addition of string-merging, token-pasting and stringizing functions\r
+      in the preprocessor;\r
+ ú    addition of trigraph translation in the preprocessor;\r
+ ú    addition of the `#pragma' directive and formalization of the\r
+      `declared()' pseudofunction in the preprocessor;\r
+ ú    introduction of multi-byte strings and characters to support non-\r
+      English languages;\r
+ ú    introduction of the `signed' keyword (to complement the `unsigned'\r
+      keyword when used in integer declarations) and the unary plus (`+')\r
+      operator.\r
+\r
+\r
+\r
+C is a medium level language\r
+\r
+The powerful facilities offered by C to allow manipulation of direct\r
+memory addresses and data, even down to the bit level, along with C's\r
+structured approach to programming cause C to be classified as a "medium\r
+level" programming language. It possesses fewer ready made facilities\r
+than a high level language, such as BASIC, but a higher level of\r
+structure than low level Assembler.\r
+\r
+\r
+Key words\r
+\r
+The original C language as described in; "The C programming language", by\r
+Kernighan and Ritchie, provided 27 key words. To those 27 the ANSI\r
+standards committee on C have added five more. This confusingly results\r
+in two standards for the C language. However, the ANSI standard is\r
+quickly taking over from the old K & R standard.\r
+\r
+\r
+The 32 C key words are;\r
+\r
+auto              double             int                struct\r
+break             else               long               switch\r
+case              enum               register           typedef\r
+char              extern             return             union\r
+const             float              short              unsigned\r
+continue          for                signed             void\r
+default           goto               sizeof             volatile\r
+do                if                 static             while\r
+\r
+Some C compilers offer additional key words specific to the hardware\r
+environment that they operate on. You should be aware of your own C\r
+compilers additional key words. Most notably on the PC these are;\r
+\r
+\r
+near      far        huge\r
+\r
+\r
+\r
+Structure\r
+\r
+C programs are written in a structured manner. A collection of code\r
+blocks are created that call each other to comprise the complete program.\r
+As a structured language C provides various looping and testing commands\r
+such as;\r
+\r
+\r
+           do-while,  for, while, if\r
+\r
+and the use of jumps, while provided for, are rarely used.\r
+\r
+A C code block is contained within a pair of curly braces "{ }", and may\r
+be a complete procedure, in C terminology called a "function", or a\r
+subset of code within a function. For example the following is a code\r
+block. The statements within the curly braces are only executed upon\r
+satisfaction of the condition that "x < 10";\r
+\r
+\r
+if (x < 10)\r
+{\r
+     a = 1;\r
+     b = 0;\r
+}\r
+\r
+while this, is a complete function code block containing a sub code block\r
+as a do-while loop;\r
+\r
+int GET_X()\r
+{\r
+     int x;\r
+\r
+     do\r
+     {\r
+          printf("\nEnter a number between 0 and 10 ");\r
+          scanf("%d",&x);\r
+     }\r
+     while(x < 0 || x > 10);\r
+     return(x);\r
+}\r
+\r
+Notice how every statement line is terminated in a semicolon, unless that\r
+statement marks the start of a code block, in which case it is followed\r
+by a curly brace. C is a case sensitive but free flow language, spaces\r
+between commands are ignored, and therefore the semicolon delimiter is\r
+required to mark the end of the command line.\r
+\r
+Having a freeflow structure the following commands are recognised as the\r
+same by the C compiler;\r
+\r
+\r
+     x = 0;\r
+     x      =0;\r
+     x=0;\r
+\r
+\r
+The general form of a C program is as follows;\r
+\r
+     compiler preprocessor statements\r
+     global data declarations\r
+     \r
+     \r
+     \r
+     \r
+     return-type main(parameter list)\r
+     {\r
+          statements\r
+     }\r
+     \r
+     return-type f1(parameter list)\r
+     {\r
+          statements\r
+     }\r
+     \r
+     return-type f2(parameter list)\r
+     {\r
+          statements\r
+     }\r
+     .\r
+     .\r
+     .\r
+     return-type fn(parameter list)\r
+     {\r
+          statements\r
+     }\r
+     \r
+\r
+\r
+Comments\r
+\r
+C allows comments to be included in the program. A comment line is\r
+defined by being enclosed within "/*" and "*/". Thus the following is a\r
+comment;\r
+\r
+\r
+/* This is a legitimate C comment line */\r
+\r
+\r
+Libraries\r
+\r
+C programs are compiled and combined with library functions provided with\r
+the C compiler. These libraries are of generally standard functions, the\r
+functionality of which are defined in the ANSI standard of the C\r
+language, but are provided by the individual C compiler manufacturers to\r
+be machine dependant. Thus, the standard library function "printf()"\r
+provides the same facilities on a DEC VAX as on an IBM PC, although the\r
+actual machine language code in the library is quite different for each.\r
+The C programmer however, does not need to know about the internals of\r
+the libraries, only that each library function will behave in the same\r
+way on any computer.\r
+\r
+\r
+                                    \r
+                               DATA TYPES\r
+\r
+\r
+There are four basic types of data in the C language; character, integer,\r
+floating point, and valueless that are referred to by the C key words;\r
+\r
+"char", "int", "float" and "void" respectively.\r
+\r
+To the basic data types may be added the type modifiers; signed,\r
+unsigned, long and short to produce further data types. By default data\r
+types are assumed signed, and the signed modifier is rarely used, unless\r
+to overide a compiler switch defaulting a data type to unsigned.\r
+\r
+The size of each data type varies from one hardware platform to another,\r
+but the least range of values that can be held is described in the ANSI\r
+standard as follows;\r
+\r
+\r
+Type                     Size                     Range\r
+                                                  \r
+char                     8                        -127 to 127\r
+unsigned char            8                        0 to 255\r
+int                      16                       -32767 to 32767\r
+unsigned int             16                       0 to 65535\r
+long int                 32                       -2147483647 to\r
+                                                  2147483647\r
+unsigned long int        32                       0 to 4294967295\r
+float                    32                       Six digit precision\r
+double                   64                       Ten digit precision\r
+long double              80                       Ten digit precision\r
+\r
+\r
+In practice, this means that the data type `char' is particularly\r
+suitable for storing flag type variables, such as status codes, which\r
+have a limited range of values. The `int' data type can be used, but if\r
+the range of values does not exceed 127 (or 255 for an unsigned char),\r
+then each declared variable would be wasting storage space.\r
+\r
+Which real number data type to use: `float', `double' or `long double' is\r
+another tricky question. When numeric accuracy is required, for example\r
+in an accounting application, the instinct would be to use the `long\r
+double', but this requires at least 10 bytes of storage space for each\r
+variable. And real numbers are not as precise as integers anyway, so\r
+perhaps one should use integer data types instead and work around the\r
+problem. The data type `float' is worse than useless since its six digit\r
+precision is too inaccurate to be relied upon. Generally, then, you\r
+should use integer data types where ever possible, but if real numbers\r
+are required use at least a `double'.\r
+\r
+\r
+Declaring a variable\r
+\r
+All variables in a C program must be declared before they can be used.\r
+The general form of a variable definition is;\r
+\r
+\r
+     type name;\r
+\r
+So, for example to declare a variable "x", of data type "int" so that it\r
+may store a value in the range -32767 to 32767, you use the statement;\r
+\r
+\r
+     int x;\r
+\r
+Character strings may be declared, which are in reality arrays of\r
+characters. They are declared as follows;\r
+\r
+\r
+     char name[number_of_elements];\r
+\r
+So, to declare a string thirty characters long, and called `name' you\r
+would use the declaration;\r
+\r
+\r
+     char name[30];\r
+\r
+\r
+Arrays of other data types also may be declared in one, two or more\r
+dimensions in the same way. For example to declare a two dimensional\r
+array of integers;\r
+\r
+\r
+     int x[10][10];\r
+\r
+The elements of this array are then accessed as;\r
+\r
+     x[0][0]\r
+     x[0][1]\r
+     x[n][n]\r
+\r
+There are three levels of access to variable; local, module and global. A\r
+variable declared within a code block is only known to the statements\r
+within that code block. A variable declared outside any function code\r
+blocks but prefixed with the storage modifier "static" is known only to\r
+the statements within that source module. A variable declared outside any\r
+functions and not prefixed with the static storage type modifier may be\r
+accessed by any statement within any source module of the program.\r
+\r
+\r
+For example;\r
+\r
+     int error;\r
+     static int a;\r
+     \r
+     main()\r
+     {\r
+          int x;\r
+          int y;\r
+     \r
+     }\r
+     \r
+     funca()\r
+     {\r
+          /* Test variable 'a' for equality with 0 */\r
+          if (a == 0)\r
+          {\r
+               int b;\r
+               for(b = 0; b < 20; b++)\r
+                    printf("\nHello World");\r
+          }\r
+     \r
+     }\r
+     \r
+\r
+In this example the variable `error' is accessible by all source code\r
+modules compiled together to form the finished program. The variable `a'\r
+is accessible by statements in both functions `main()' and `funca()', but\r
+is invisible to any other source module. Variables `x' and `y' are only\r
+accessible by statements within function `main()'. The variable `b' is\r
+only accessible by statements within the code block following the `if'\r
+statement.\r
+\r
+If a second source module wished to access the variable `error' it would\r
+need to declare `error' as an `extern' global variable thus;\r
+\r
+\r
+     extern int error;\r
+     \r
+     funcb()\r
+     {\r
+     }\r
+     \r
+C will quite happily allow you, the programmer, to assign different data\r
+types to each other. For example, you may declare a variable to be of\r
+type `char' in which case a single byte of data will be allocated to\r
+store the variable. To this variable you can attempt to allocate larger\r
+values, for example;\r
+\r
+\r
+     main()\r
+     {\r
+     \r
+          x = 5000;\r
+     \r
+     }\r
+     \r
+In this example the variable `x' can only store a value between -127 and\r
+128, so the figure 5000 will NOT be assigned to the variable `x'. Rather\r
+the value 136 will be assigned!\r
+\r
+\r
+Often you may wish to assign different data types to each other, and to\r
+prevent the compiler from warning you of a possible error you can use a\r
+cast to tell the compiler that you know what you're doing. A cast\r
+statement is a data type in parenthesis preceding a variable or\r
+expression;\r
+\r
+\r
+     main()\r
+     {\r
+          float x;\r
+          int y;\r
+     \r
+          x = 100 / 25;\r
+     \r
+          y = (int)x;\r
+     }\r
+\r
+In this example the (int) cast tells the compiler to convert the value of\r
+the floating point variable x to an integer before assigning it to the\r
+variable y.\r
+\r
+\r
+\r
+Formal parameters\r
+\r
+A C function may receive parameters from a calling function. These\r
+parameters are declared as variables within the parentheses of the\r
+function name, thus;\r
+\r
+\r
+     int MULT(int x, int y)\r
+     {\r
+          /* Return parameter x multiplied by parameter y */\r
+          return(x * y);\r
+     }\r
+     \r
+     main()\r
+     {\r
+          int a;\r
+          int b;\r
+          int c;\r
+     \r
+          a = 5;\r
+          b = 7;\r
+          c = MULT(a,b);\r
+     \r
+          printf("%d multiplied by %d equals %d\n",a,b,c);\r
+     }\r
+     \r
+\r
+Access modifiers\r
+\r
+There are two access modifiers; `const' and `volatile'. A variable\r
+declared to be `const' may not be changed by the program, whereas a\r
+variable declared as type  as type `volatile' may be changed by the\r
+program. In addition, declaring a variable to be volatile prevents the C\r
+compiler from allocating the variable to a register, and reduces the\r
+optimization carried out on the variable.\r
+\r
+\r
+\r
+Storage class types\r
+C provides four storage types; `extern', `static', `auto' and `register'.\r
+\r
+The extern storage type is used to allow a source module within a C\r
+program to access a variable declared in another source module.\r
+\r
+Static variables are only accessible within the code block that declared\r
+them, and additionally if the variable is local, rather than global, they\r
+retain their old value between subsequent calls to the code block.\r
+\r
+Register variables are stored within CPU registers where ever possible,\r
+providing the fastest possible access to their values.\r
+\r
+The auto type variable is only used with local variables, and declares\r
+the variable to retain its value locally only. Since this is the default\r
+for local variables the auto storage type is very rarely used.\r
+                                    \r
+                                    \r
+                                    \r
+                                OPERATORS\r
+\r
+Operators are tokens that cause a computation to occur when applied to\r
+variables. C provides the following operators;\r
+\r
+\r
+&                  Address\r
+*                  Indirection\r
++                  Unary plus\r
+-                  Unary minus\r
+~                  Bitwise compliment\r
+!                  Logical negation\r
+++                 As a prefix;\r
+                   preincrement\r
+                   As a suffix;\r
+                   postincrement\r
+--                 As a prefix;\r
+                   predecrement\r
+                   As a suffix;\r
+                   postdecrement\r
++                  Addition\r
+-                  Subtraction\r
+*                  Multiply\r
+/                  Divide\r
+%                  Remainder\r
+<<                 Shift left\r
+>>                 Shift right\r
+&                  Bitwise AND\r
+|                  Bitwise OR\r
+^                  Bitwise XOR\r
+&&                 Logical AND\r
+||                 Logical OR\r
+=                  Assignment\r
+*=                 Assign product\r
+/=                 Assign quotient\r
+%=                 Assign remainder\r
+                   (modulus)\r
++=                 Assign sum\r
+-=                 Assign difference\r
+<<=                Assign left shift\r
+>>=                Assign right shift\r
+&=                 Assign bitwise AND\r
+|=                 Assign bitwise OR\r
+^=                 Assign bitwise XOR\r
+<                  Less than\r
+>                  Greater than\r
+<=                 Less than or equal\r
+                   to\r
+>=                 Greater than or\r
+                   equal to\r
+==                 Equal to\r
+!=                 Not equal to\r
+.                  Direct component\r
+                   selector\r
+->                 Indirect component\r
+                   selector\r
+a ? x:y            "If a is true then\r
+                   x else y"\r
+[]                 Define arrays\r
+()                 Parenthesis\r
+                   isolate conditions\r
+                   and expressions\r
+...                Ellipsis are used\r
+                   in formal\r
+                   parameter lists of\r
+                   function\r
+                   prototypes to show\r
+                   a variable number\r
+                   of\r
+                   parameters or\r
+                   parameters of\r
+                   varying types.\r
+\r
+To illustrate some more commonly used operators consider the following\r
+short program;\r
+\r
+\r
+     main()\r
+     {\r
+          int a;\r
+          int b;\r
+          int c;\r
+          a = 5;           /* Assign a value of 5 to variable 'a' */\r
+          b = a / 2;       /* Assign the value of 'a' divided by two to\r
+     variable 'b' */\r
+          c = b * 2;       /* Assign the value of 'b' multiplied by two\r
+     to variable 'c' */\r
+     \r
+          if (a == c)     /* Test if 'a' holds the same value as 'c' */\r
+     \r
+               puts("Variable 'a' is an even number");\r
+          else\r
+               puts("Variable 'a' is an odd number");\r
+     }\r
+     \r
+     \r
+Normally when incrementing the value of a variable you would write\r
+something like;\r
+\r
+          x = x + 1\r
+\r
+C provides the incremental operator '++' as well so that you can write;\r
+\r
+          x++\r
+\r
+Similarly you can decrement the value of a variable using '--' as;\r
+\r
+          x--\r
+\r
+All the other mathematical operators may be used the same, so in a C\r
+program you can write in shorthand;\r
+\r
+\r
+NORMAL                               C\r
+                                     \r
+x = x + 1                            x++\r
+x = x - 1                            x--\r
+x = x * 2                            x *= 2\r
+x = x / y                            x /= y\r
+x = x % 5                            x %= 5\r
+\r
+and so on.\r
+                                    \r
+                                    \r
+                                    \r
+                                FUNCTIONS\r
+\r
+Functions are the source code procedures that comprise a C program. They\r
+follow the general form;\r
+\r
+     return_type function_name(parameter_list)\r
+     {\r
+          statements\r
+     }\r
+\r
+\r
+The return_type specifies the data type that will be returned by the\r
+function; char, int, double, void &c.\r
+\r
+The code within a C function is invisible to any other C function, and\r
+jumps may not be made from one function into the middle of another,\r
+although functions may call other functions. Also, functions cannot be\r
+defined within functions, only within source modules.\r
+\r
+Parameters may be passed to a function either by value, or by reference.\r
+If a parameter is passed by value, then only a copy of the current value\r
+of the parameter is passed to the function. A parameter passed by\r
+reference however, is a pointer to the actual parameter that may then be\r
+changed by the function.\r
+\r
+\r
+The following example passes two parameters by value to a function,\r
+funca(), which attempts to change the value of the variables passed to\r
+it. And then passes the same two parameters by reference to funcb() which\r
+also attempts to modify their values.\r
+\r
+\r
+     #include <stdio.h>\r
+     \r
+     int funca(int x, int y)\r
+     {\r
+          /* This function receives two parameters by value, x and y */\r
+     \r
+          x = x * 2;\r
+          y = y * 2;\r
+     \r
+          printf("\nValue of x in funca() %d value of y in funca()\r
+     %d",x,y);\r
+     \r
+          return(x);\r
+     }\r
+     \r
+     \r
+     int funcb(int *x, int *y)\r
+     {\r
+          /* This function receives two parameters by reference, x and y\r
+     */\r
+     \r
+          *x = *x * 2;\r
+          *y = *y * 2;\r
+     \r
+          printf("\nValue of x in funcb() %d value of y in funcb()\r
+     %d",*x,*y);\r
+     \r
+          return(*x);\r
+     }\r
+     \r
+     main()\r
+     {\r
+          int x;\r
+          int y;\r
+          int z;\r
+     \r
+          x = 5;\r
+          y = 7;\r
+     \r
+          z = funca(x,y);\r
+          z = funcb(&x,&y);\r
+     \r
+          printf("\nValue of x %d value of y %d value of z %d",x,y,z);\r
+     }\r
+     \r
+\r
+Actually funcb() does not change the values of the parameters it\r
+receives.  Rather it changes the contents of the memory addresses pointed\r
+to by the received parameters. While funca() receives the values of\r
+variables `x' and `y' from function main(), funcb() receives the memory\r
+addresses of the variables `x' and `y' from function main().\r
+\r
+\r
+\r
+Passing an array to a function\r
+\r
+The following program passes an array to a function, funca(), which\r
+initialises the array elements;\r
+\r
+     #include <stdio.h>\r
+     \r
+     void funca(int x[])\r
+     {\r
+          int n;\r
+     \r
+          for(n = 0; n < 100; n++)\r
+          x[n] = n;\r
+     }\r
+     \r
+     main()\r
+     {\r
+          int array[100];\r
+          int counter;\r
+     \r
+          funca(array);\r
+     \r
+          for(counter = 0; counter < 100; counter++)\r
+               printf("\nValue of element %d is\r
+     %d",counter,array[counter]);\r
+     }\r
+\r
+The parameter of funca() `int x[]' is declared to be an array of any\r
+length.  This works because the compiler passes the address of the start\r
+of the array parameter to the function, rather than the value of the\r
+individual elements.  This does of course mean that the function can\r
+change the value of the array elements. To prevent a function from\r
+changing the values you can specify the parameter as type `const';\r
+\r
+\r
+     funca(const int x[])\r
+     {\r
+     }\r
+\r
+This will then generate a compiler error at the line that attempts to\r
+write a value to the array. However, specifying a parameter to be const\r
+does not protect the parameter from indirect assignment as the following\r
+program illustrates;\r
+\r
+\r
+     #include <stdio.h>\r
+     \r
+     int funca(const int x[])\r
+     {\r
+          int *ptr;\r
+          int n;\r
+     \r
+          /* This line gives a 'suspicious pointer conversion warning' */\r
+          /* because x is a const pointer, and ptr is not */\r
+          ptr = x;\r
+     \r
+          for(n = 0; n < 100; n++)\r
+          {\r
+               *ptr = n;\r
+               ptr++;\r
+          }\r
+     }\r
+     \r
+     main()\r
+     {\r
+          int array[100];\r
+          int counter;\r
+     \r
+          funca(array);\r
+     \r
+          for(counter = 0; counter < 100; counter++)\r
+               printf("\nValue of element %d is\r
+     %d",counter,array[counter]);\r
+     }\r
+     \r
+\r
+\r
+Passing parameters to main()\r
+\r
+C allows parameters to be passed from the operating system to the program\r
+when it starts executing through two parameters; argc and argv[], as\r
+follows;\r
+\r
+\r
+     #include <stdio.h>\r
+     \r
+     main(int argc, char *argv[])\r
+     {\r
+          int n;\r
+     \r
+          for(n = 0; n < argc; n++)\r
+          printf("\nParameter %d equals %s",n,argv[n]);\r
+     }\r
+     \r
+\r
+Parameter argc holds the number of parameters passed to the program, and\r
+the array argv[] holds the addresses of each parameter passed. argv[0] is\r
+always the program name. This feature may be put to good use in\r
+applications that need to access system files. Consider the following\r
+scenario:\r
+\r
+A simple database application stores its data in a single file called\r
+"data.dat". The application needs to be created so that it may be stored\r
+in any directory on either a floppy diskette or a hard disk, and executed\r
+both from within the host directory and through a DOS search path. To\r
+work correctly the application must always know where to find the data\r
+file; "data.dat". This is solved by assuming that the data file will be\r
+in the same directory as the executable module, a not unreasonable\r
+restriction to place upon the operator. The following code fragment then\r
+illustrates how an application may apply this algorithm into practice to\r
+be always able to locate a desired system file:\r
+\r
+\r
+     #include <string.h>\r
+     \r
+     char system_file_name[160];\r
+     \r
+     void main(int argc,char *argv[])\r
+     {\r
+          char *data_file = "DATA.DAT";\r
+          char *p;\r
+     \r
+          strcpy(system_file_name,argv[0]);\r
+          p = strstr(system_file_name,".EXE");\r
+          if (p == NULL)\r
+          {\r
+               /* The executable is a .COM file */\r
+             p = strstr(system_file_name,".COM");\r
+          }\r
+     \r
+          /* Now back track to the last '\' character in the file name */\r
+          while(*(p - 1) != '\\')\r
+               p--;\r
+     \r
+          strcpy(p,data_file);\r
+     }\r
+\r
+In practice this code creates a string in system_file_name that is\r
+comprised of path\data.dat, so if for example the executable file is\r
+called "test.exe" and resides in the directory \borlandc, then\r
+system_file_name will be assigned with: \borlandc\data.dat\r
+\r
+\r
+\r
+Returning from a function\r
+\r
+The command `return' is used to return immediately from a function. If\r
+the function was declared with a return data type, then return should be\r
+used with a parameter of the same data type.\r
+\r
+\r
+\r
+Function prototypes\r
+\r
+Prototypes for functions allow the C compiler to check that the type of\r
+data being passed to and from functions is correct. This is very\r
+important to prevent data overflowing its allocated storage space into\r
+other variables areas.\r
+\r
+A function prototype is placed at the beginning of the program, after any\r
+preprocessor commands, such as #include <stdio.h>, and before the\r
+declaration of any functions.\r
+                                    \r
+                           THE C PREPROCESSOR\r
+\r
+C allows for commands to the compiler to be included in the source code.\r
+These commands are then called preprocessor commands and are defined by\r
+the ANSI standard to be;\r
+\r
+\r
+    #if\r
+    #ifdef\r
+    #ifndef\r
+    #else\r
+    #elif\r
+    #include\r
+    #define\r
+    #undef\r
+    #line\r
+    #error\r
+    #pragma\r
+\r
+All preprocessor commands start with a hash symbol, "#", and must be on a\r
+line on their own (although comments may follow).\r
+\r
+\r
+\r
+#define\r
+\r
+The #define command specifies an identifier and a string that the\r
+compiler will substitute every time it comes accross the identifier\r
+within that source code module. For example;\r
+\r
+\r
+#define FALSE 0\r
+#define TRUE !FALSE\r
+\r
+The compiler will replace any subsequent occurence of `FALSE' with `0'\r
+and any subsequent occurence of `TRUE' with `!0'. The substitution does\r
+NOT take place if the compiler finds that the identifier is enclosed by\r
+quotation marks, so\r
+\r
+\r
+     printf("TRUE");\r
+\r
+would NOT be replaced, but\r
+\r
+     printf("%d",FALSE);\r
+\r
+would be.\r
+\r
+The #define command also can be used to define macros that may include\r
+parameters. The parameters are best enclosed in parenthesis to ensure\r
+that correct substitution occurs.\r
+\r
+\r
+This example declares a macro `larger()' that accepts two parameters and\r
+returns the larger of the two;\r
+\r
+     #include <stdio.h>\r
+     \r
+     #define larger(a,b) (a > b) ? (a) : (b)\r
+     \r
+     int main()\r
+     {\r
+          printf("\n%d is largest",larger(5,7));\r
+     \r
+     }\r
+\r
+\r
+#error\r
+\r
+The #error command causes the compiler to stop compilation and to display\r
+the text following the #error command. For example;\r
+\r
+\r
+#error REACHED MODULE B\r
+\r
+will cause the compiler to stop compilation and display;\r
+\r
+     REACHED MODULE B\r
+\r
+\r
+\r
+#include\r
+\r
+The #include command tells the compiler to read the contents of another\r
+source file. The name of the source file must be enclosed either by\r
+quotes or by angular brackets thus;\r
+\r
+\r
+     #include "module2.c"\r
+     #include <stdio.h>\r
+\r
+Generally, if the file name is enclosed in angular brackets, then the\r
+compiler will search for the file in a directory defined in the\r
+compiler's setup. Whereas if the file name is enclosed in quotes then the\r
+compiler will look for the file in the current directory.\r
+\r
+\r
+\r
+#if, #else, #elif, #endif\r
+\r
+The #if set of commands provide conditional compilation around the\r
+general form;\r
+\r
+     #if constant_expression\r
+          statements\r
+     #else\r
+          statements\r
+     #endif\r
+\r
+#elif stands for '#else if' and follows the form;\r
+\r
+     #if expression\r
+          statements\r
+     #elif expression\r
+          statements\r
+     #endif\r
+\r
+\r
+\r
+#ifdef, #ifndef\r
+\r
+These two commands stand for '#if defined' and '#if not defined'\r
+respectively and follow the general form;\r
+\r
+     #ifdef macro_name\r
+          statements\r
+     #else\r
+          statements\r
+     #endif\r
+\r
+     #ifndef macro_name\r
+          statements\r
+     #else\r
+          statements\r
+     #endif\r
+\r
+where 'macro_name' is an identifier declared by a #define statement.\r
+\r
+\r
+\r
+#undef\r
+\r
+Undefines a macro previously defined by #define.\r
+\r
+\r
+#line\r
+\r
+Changes the compiler declared global variables __LINE__ and __FILE__. The\r
+general form of #line is;\r
+\r
+     #line number "filename"\r
+\r
+where number is inserted into the variable '__LINE__' and 'filename' is\r
+assigned to '__FILE__'.\r
+\r
+\r
+#pragma\r
+\r
+This command is used to give compiler specific commands to the compiler.\r
+The compiler's manual should give you full details of any valid options\r
+to go with the particular implementation of #pragma that it supports.\r
+                                    \r
+                       PROGRAM CONTROL STATEMENTS\r
+\r
+As with any computer language, C includes statements that test the\r
+outcome of an expression. The outcome of the test is either TRUE or\r
+FALSE. The C language defines a value of TRUE as non-zero, and FALSE as\r
+zero.\r
+\r
+\r
+\r
+Selection statements\r
+\r
+The general purpose selection statement is "if" that follows the general\r
+form;\r
+\r
+          if (expression)\r
+               statement\r
+          else\r
+               statement\r
+\r
+Where "statement" may be a single statement, or a code block enclosed in\r
+curly braces. The "else" is optional. If the result of the expression\r
+equates to TRUE, then the statement(s) following the if() will be\r
+evaluated.  Otherwise the statement(s) following the else, if there is\r
+one, will be evaluated.\r
+\r
+\r
+An alternative to the if....else combination is the ?: command that takes\r
+the form;\r
+\r
+\r
+           expression ? true_expression : false_expression\r
+\r
+Where if the expression evaluates to TRUE, then the true_expression will\r
+be evaluated, otherwise the false_expression will be evaluated. Thus we\r
+get;\r
+\r
+\r
+     #include <stdio.h>\r
+     \r
+     main()\r
+     {\r
+          int x;\r
+     \r
+          x = 6;\r
+     \r
+          printf("\nx is an %s number", x % 2 == 0 ? "even" : "odd");\r
+     }\r
+     \r
+C also provides a multiple branch selection statement, switch, which\r
+successively tests a value of an expression against a list of values and\r
+branches program execution to the first match found. The general form of\r
+switch is;\r
+\r
+\r
+     switch (expression)\r
+     {\r
+          case value1 :  statements\r
+                    break;\r
+          case value2 :  statements\r
+                    break;\r
+          .\r
+          .\r
+          .\r
+          .\r
+          case valuen :  statements\r
+                    break;\r
+          default :      statements\r
+     }\r
+\r
+The break statement is optional, but if omitted, program execution will\r
+continue down the list.\r
+\r
+     #include <stdio.h>\r
+     \r
+     main()\r
+     {\r
+          int x;\r
+     \r
+          x = 6;\r
+     \r
+          switch(x)\r
+          {\r
+               case 0 : printf("\nx equals zero");\r
+                     break;\r
+               case 1 : printf("\nx equals one");\r
+                     break;\r
+               case 2 : printf("\nx equals two");\r
+                     break;\r
+               case 3 : printf("\nx equals three");\r
+                     break;\r
+               default : printf("\nx is larger than three");\r
+          }\r
+     }\r
+\r
+Switch statements may be nested within one another. This is a\r
+particularly useful feature for confusing people who read your source\r
+code!\r
+\r
+\r
+Iteration statements\r
+C provides three looping or iteration statements; for, while, and do-\r
+while. The for loop has the general form;\r
+\r
+\r
+     for(initialization;condition;increment)\r
+\r
+and is useful for counters such as in this example that displays the\r
+entire ascii character set;\r
+\r
+     #include <stdio.h>\r
+     \r
+     main()\r
+     {\r
+          int x;\r
+     \r
+          for(x = 32; x < 128; x++)\r
+               printf("%d\t%c\t",x,x);\r
+     }\r
+     \r
+An infinite for loop is also quite valid;\r
+\r
+     for(;;)\r
+     {\r
+          statements\r
+     }\r
+\r
+Also, C allows empty statements. The following for loop removes leading\r
+spaces from a string;\r
+\r
+     for(; *str == ' '; str++)\r
+          ;\r
+\r
+Notice the lack of an initializer, and the empty statement following the\r
+loop.\r
+\r
+The while loop is somewhat simpler than the for loop and follows the\r
+general form;\r
+\r
+     while (condition)\r
+          statements\r
+\r
+The statement following the condition, or statements enclosed in curly\r
+braces will be executed until the condition is FALSE. If the condition is\r
+false before the loop commences, the loop statements will not be\r
+executed. The do-while loop on the other hand is always executed at least\r
+once. It takes the general form;\r
+\r
+\r
+     do\r
+     {\r
+          statements\r
+     }\r
+     while(condition);\r
+\r
+\r
+Jump statements\r
+\r
+The "return" statement is used to return from a function to the calling\r
+function. Depending upon the declared return data type of the function it\r
+may or may not return a value;\r
+\r
+\r
+     int MULT(int x, int y)\r
+     {\r
+          return(x * y);\r
+     }\r
+\r
+or;\r
+\r
+     void FUNCA()\r
+     {\r
+          printf("\nHello World");\r
+          return;\r
+     }\r
+\r
+The "break" statement is used to break out of a loop or from a switch\r
+statement. In a loop it may be used to terminate the loop prematurely, as\r
+shown here;\r
+\r
+\r
+     #include <stdio.h>\r
+     \r
+     main()\r
+     {\r
+          int x;\r
+     \r
+          for(x = 0; x < 256; x++)\r
+          {\r
+               if (x == 100)\r
+                    break;\r
+     \r
+               printf("%d\t",x);\r
+          }\r
+     }\r
+\r
+In contrast to "break" is "continue", which forces the next iteration of\r
+the loop to occur, effectively forcing program control back to the loop\r
+statement.\r
+\r
+\r
+C provides a function for terminating the program prematurely, "exit()".\r
+Exit() may be used with a return value to pass back to the calling\r
+program;\r
+\r
+\r
+     exit(return_value);\r
+\r
+\r
+More About ?:\r
+\r
+\r
+A powerful, but often misunderstood feature of the C programming language\r
+is ?:. This is an operator that acts upon a boolean expression, and\r
+returns one of two values dependant upon the result of the expression;\r
+\r
+\r
+     <boolean expression> ? <value for true> : <value for false>\r
+\r
+It can be used almost anywhere, for example it was used in the binary\r
+search demonstration program;\r
+\r
+     printf("\n%s\n",(result == 0) ? "Not found" : "Located okay");\r
+\r
+Here it passes either "Not found" or "Located okay" to the printf()\r
+function dependant upon the outcome of the boolean expression `result ==\r
+0'. Alternatively it can be used for assigning values to a variable;\r
+\r
+     x = (a  == 0) ? (b) : (c);\r
+\r
+Which will assign the value of b to variable x if a is equal to zero,\r
+otherwise it will assign the value of c to variable x.\r
+\r
+This example returns the name of the executing program, without any path\r
+description;\r
+\r
+     #include <stdio.h>\r
+     #include <stddef.h>\r
+     #include <string.h>\r
+     \r
+     char *progname(char *pathname)\r
+     {\r
+          /* Return name of running program */\r
+          unsigned l;\r
+          char *p;\r
+          char *q;\r
+          static char bnbuf[256];\r
+     \r
+          return pathname? p = strrchr (pathname, '\\'),\r
+                     q = strrchr (pathname, '.'),\r
+                     l = (q == NULL? strchr (pathname, '\0'): q)\r
+                       - (p == NULL? p = pathname: ++p),\r
+                     strncpy (bnbuf, p, l),\r
+                     bnbuf[l] = '\0',\r
+                     strlwr (bnbuf)\r
+                  : NULL;\r
+     }\r
+     \r
+     void main(int argc, char *argv[])\r
+     {\r
+          printf("\n%s",progname(argv[0]));\r
+     }\r
+     \r
+     \r
+\r
+Continue\r
+\r
+The continue keyword forces control to jump to the test statement of the\r
+innermost loop (while, do...while()). This can be useful for terminating\r
+a loop gracefuly as this program that reads strings from a file until\r
+there are no more illustrates;\r
+\r
+\r
+     #include <stdio.h>\r
+     \r
+     void main()\r
+     {\r
+          FILE *fp;\r
+          char *p;\r
+          char buff[100];\r
+     \r
+          fp = fopen("data.txt","r");\r
+          if (fp == NULL)\r
+          {\r
+               fprintf(stderr,"Unable to open file data.txt");\r
+               exit(0);\r
+          }\r
+     \r
+          do\r
+          {\r
+               p = fgets(buff,100,fp);\r
+               if (p == NULL)\r
+                    /* Force exit from loop */\r
+                    continue;\r
+               puts(p);\r
+          }\r
+          while(p);\r
+     }\r
+     \r
+With a for() loop however, continue passes control back to the third\r
+parameter!\r
+                                    \r
+                            INPUT AND OUTPUT\r
+\r
+\r
+\r
+Input\r
+Input to a C program may occur from the console, the standard input\r
+device (unless otherwise redirected this is the console), from a file or\r
+from a data port.\r
+\r
+The general input command for reading data from the standard input stream\r
+`stdin' is scanf(). Scanf() scans a series of input fields, one character\r
+at a time. Each field is then formatted according to the appropriate\r
+format specifier passed to the scanf() function as a parameter. This\r
+field is then stored at the ADDRESS passed to scanf() following the\r
+format specifiers list.\r
+\r
+For example, the following program will read a single integer from the\r
+stream stdin;\r
+\r
+\r
+     main()\r
+     {\r
+          int x;\r
+     \r
+          scanf("%d",&x);\r
+     }\r
+\r
+Notice the address operator & prefixing the variable name `x' in the\r
+scanf() parameter list. This is because scanf() stores values at\r
+ADDRESSES rather than assigning values to variables directly.\r
+\r
+The format string is a character string that may contain three types of\r
+data:\r
+\r
+whitespace characters (space, tab and newline), non-whitespace characters\r
+(all ascii characters EXCEPT %) and format specifiers.\r
+\r
+Format specifiers have the general form;\r
+\r
+\r
+     %[*][width][h|l|L]type_character\r
+\r
+After the % sign the format specifier is comprised of:\r
+\r
+ an optional assignment suppression character, *, which suppresses\r
+ assignment of the next input field.\r
\r
+ an optional width specifier, width, which declares the maximum number\r
+ of characters to be read.\r
\r
+ an optional argument type modifier, h or l or L, where:\r
+           h is a short integer\r
+           l is a long\r
+           L is a long double\r
+\r
+     the data type character that is one of;\r
+\r
+\r
+d      Decimal integer\r
+D      Decimal long integer\r
+o      Octal integer\r
+O      Octal long integer\r
+i      Decimal, octal or hexadecimal integer\r
+I      Decimal, octal or hexadecimal long\r
+       integer\r
+u      Decimal unsigned integer\r
+U      Decimal unsigned long integer\r
+x      Hexadecimal integer\r
+X      Hexadecimal long integer\r
+e      Floating point\r
+f      Floating point\r
+g      Floating point\r
+s      Character string\r
+c      Character\r
+%      % is stored\r
+\r
+An example using scanf();\r
+\r
+\r
+     #include <stdio.h>\r
+     \r
+     main()\r
+     {\r
+          char name[30];\r
+          int age;\r
+     \r
+          printf("\nEnter your name and age ");\r
+          scanf("%30s%d",name,&age);\r
+          printf("\n%s %d",name,age);\r
+     }\r
+\r
+Notice the include line, "#include <stdio.h>", this is to tell the\r
+compiler to also read the file stdio.h that contains the function\r
+prototypes for scanf() and printf().\r
+\r
+\r
+If you type in and run this sample program you will see that only one\r
+name can be entered, that is you can't enter;\r
+\r
+\r
+     JOHN SMITH\r
+\r
+because scanf() detects the whitespace between "JOHN" and "SMITH" and\r
+moves on to the next input field, which is age, and attempts to assign\r
+the value "SMITH" to the age field! The limitations of scanf() as an\r
+input function are obvious.\r
+\r
+An alternative input function is gets() that reads a string of characters\r
+from the stream stdin until a newline character is detected. The newline\r
+character is replaced by a null (0 byte) in the target string. This\r
+function has the advantage of allowing whitespace to be read in. The\r
+following program is a modification to the earlier one, using gets()\r
+instead of scanf().\r
+\r
+\r
+     #include <stdio.h>\r
+     #include <stdlib.h>\r
+     #include <string.h>\r
+     \r
+     main()\r
+     {\r
+          char data[80];\r
+          char *p;\r
+          char name[30];\r
+          int age;\r
+     \r
+          printf("\nEnter your name and age ");\r
+          /* Read in a string of data */\r
+          gets(data);\r
+     \r
+     \r
+          /* P is a pointer to the last character in the input string */\r
+          p = &data[strlen(data) - 1];\r
+     \r
+          /* Remove any trailing spaces by replacing them with null bytes\r
+     */\r
+          while(*p == ' '){\r
+               *p = 0;\r
+               p--;\r
+          }\r
+     \r
+          /* Locate last space in the string */\r
+          p = strrchr(data,' ');\r
+     \r
+          /* Read age from string and convert to an integer */\r
+          age = atoi(p);\r
+     \r
+          /* Terminate data string at start of age field */\r
+          *p = 0;\r
+     \r
+          /* Copy data string to name variable */\r
+          strcpy(name,data);\r
+     \r
+          /* Display results */\r
+          printf("\nName is %s age is %d",name,age);\r
+     }\r
+\r
+\r
+Output\r
+The most common output function is printf() that is very similar to\r
+scanf() except that it writes formatted data out to the standard output\r
+stream stdout.  Printf() takes a list of output data fields and applies\r
+format specifiers to each and outputs the result. The format specifiers\r
+are the same as for scanf() except that flags may be added to the format\r
+specifiers. These flags include;\r
+\r
+\r
+     - Which left justifies the output padding to the right with spaces.\r
+     + Which causes numbers to be prefixed by their sign\r
+\r
+The width specifier is also slightly different for printf(). In its most\r
+useful form is the precision specifier;\r
+\r
+\r
+     width.precision\r
+\r
+So, to print a floating point number to three decimal places you would\r
+use;\r
+\r
+     printf("%.3f",x);\r
+\r
+And to pad a string with spaces to the right or truncate the string to\r
+twenty characters if it is longer, to form a fixed twenty character\r
+width;\r
+\r
+     printf("%-20.20s",data);\r
+\r
+Special character constants may appear in the printf() parameter list.\r
+These are;\r
+\r
+\n         Newline\r
+\r         Carriage return\r
+\t         Tab\r
+\b         Sound the computer's bell\r
+\f         Formfeed\r
+\v         Vertical tab\r
+\\         Backslash character\r
+\'         Single quote\r
+\"         Double quote\r
+\?         Question mark\r
+\O         Octal string\r
+\x         Hexadecimal string\r
+\r
+Thus, to display "Hello World", surrounded in quotation marks and\r
+followed by a newline you would use;\r
+\r
+     printf("\"Hello World\"\n");\r
+\r
+The following program shows how a decimal integer may be displayed as a\r
+decimal, hexadecimal or octal integer. The 04 following the % in the\r
+printf() format tells the compiler to pad the displayed figure to a width\r
+of at least four digits padded with leading zeroes if required.\r
+\r
+\r
+     /* A simple decimal to hexadecimal and octal conversion program */\r
+     \r
+     #include <stdio.h>\r
+     \r
+     main()\r
+     {\r
+          int x;\r
+     \r
+          do\r
+          {\r
+               printf("\nEnter a number, or 0 to end ");\r
+               scanf("%d",&x);\r
+               printf("%04d  %04X  %04o",x,x,x);\r
+          }\r
+          while(x != 0);\r
+     \r
+     }\r
+     \r
+There are associated functions to printf() that you should be aware of.\r
+fprintf() has the prototype;\r
+\r
+     fprintf(FILE *fp,char *format[,argument,...]);\r
+\r
+This variation on printf() simply sends the formatted output to the\r
+specified file stream.\r
+\r
+sprintf() has the function prototype;\r
+\r
+     sprintf(char *s,char *format[,argument,...]);\r
+\r
+and writes the formatted output to a string. You should take care using\r
+sprintf() that the target string has been declared long enough to hold\r
+the result of the sprintf() output. Otherwise other data will be\r
+overwritten in memory.\r
+\r
+An example of using sprintf() to copy multiple data to a string;\r
+\r
+     #include<stdio.h>\r
+     \r
+     int main()\r
+     {\r
+          char buffer[50];\r
+     \r
+          sprintf(buffer,"7 * 5 == %d\n",7 * 5);\r
+     \r
+          puts(buffer);\r
+     }\r
+     \r
+\r
+An alternative to printf() for outputting a simple string to the stream\r
+stdout is puts(). This function sends a string to the stream stdout\r
+followed by a newline character. It is faster than printf(), but far less\r
+flexible. Instead of;\r
+\r
+     printf("Hello World\n");\r
+\r
+You can use;\r
+\r
+     puts("Hello World");\r
+\r
+\r
+\r
+Direct Console I/O\r
+Data may be sent to, and read directly from the console (keyboard and\r
+screen) using the direct console I/O functions. These functions are\r
+prefixed `c'. The direct console I/O equivalent of printf() is then\r
+cprintf(), and the equivalent of puts() is cputs(). Direct console I/O\r
+functions differ from standard I?o functions:\r
+\r
+  1. They do not make use of the predefined streams, and hence may not be\r
+     redirected\r
+  2. They are not portable across operating systems (for example you cant\r
+     use direct console I/O functions in a Windows programme).\r
+  3. They are faster than their standard I/O equivalents\r
+  4. They may not work with all video modes (especially VESA display\r
+     modes).\r
+                                    \r
+                                    \r
+                                    \r
+                                POINTERS\r
+\r
+A pointer is a variable that holds the memory address of an item of data.\r
+Therefore it points to another item. A pointer is declared like an\r
+ordinary variable, but its name is prefixed by `*', thus;\r
+\r
+\r
+     char *p;\r
+\r
+This declares the variable `p' to be a pointer to a character variable.\r
+Pointers are very powerful, and similarly dangerous! If only because a\r
+pointer can be inadvertently set to point to the code segment of a\r
+program and then some value assigned to the address of the pointer!\r
+\r
+The following program illustrates a simple, though fairly useless\r
+application of a pointer;\r
+\r
+\r
+     #include <stdio.h>\r
+     \r
+     main()\r
+     {\r
+          int a;\r
+          int *x;\r
+     \r
+          /* x is a pointer to an integer data type */\r
+     \r
+          a = 100;\r
+          x = &a;\r
+     \r
+          printf("\nVariable 'a' holds the value %d at memory address\r
+     %p",a,x);\r
+     }\r
+\r
+Here is a more useful example of a pointer illustrating how because the\r
+compiler knows the type of data pointed to by the pointer, when the\r
+pointer is incremented it is incremented the correct number of bytes for\r
+the data type.  In this case two bytes;\r
+\r
+     #include <stdio.h>\r
+     \r
+     main()\r
+     {\r
+          int n;\r
+          int a[25];\r
+          int *x;\r
+     \r
+          /* x is a pointer to an integer data type */\r
+     \r
+          /* Assign x to point to array element zero */\r
+          x = a;\r
+     \r
+          /* Assign values to the array */\r
+          for(n = 0; n < 25; n++)\r
+               a[n] = n;\r
+     \r
+     \r
+          /* Now print out all array element values */\r
+          for(n = 0; n < 25; n++ , x++)\r
+               printf("\nElement %d holds %d",n,*x);\r
+     }\r
+     \r
+To read or assign a value to the address held by a pointer you use the\r
+indirection operator `*'. Thus in the above example, to print the value\r
+at the memory address pointed to by variable x I have used `*x'.\r
+\r
+Pointers may be incremented and decremented and have other mathematics\r
+applied to them also. For example in the above program to move variable x\r
+along the array one element at a time we put the statement `x++' in the\r
+for loop. We could move x along two elements by saying `x += 2'. Notice\r
+that this doesn't mean "add 2 bytes to the value of x", but rather it\r
+means "add 2 of the pointer's data type size units to the value of x".\r
+\r
+Pointers are used extensively in dynamic memory allocation. When a\r
+program is running it is often necessary to temporarily allocate a block\r
+of data, say a table, in memory. C provides the function malloc() for\r
+this purpose that follows the general form;\r
+\r
+\r
+     any pointer type = malloc(number_of_bytes);\r
+\r
+malloc() actually returns a void pointer type, which means it can be any\r
+type; integer, character, floating point or whatever. This example\r
+allocates a table in memory for 1000 integers;\r
+\r
+     #include <stdio.h>\r
+     #include <stdlib.h>\r
+     \r
+     main()\r
+     {\r
+          int *x;\r
+          int n;\r
+     \r
+          /* x is a pointer to an integer data type */\r
+     \r
+          /* Create a 1000 element table, sizeof() returns the compiler\r
+     */\r
+          /* specific number of bytes used to store an integer */\r
+     \r
+          x = malloc(1000 * sizeof(int));\r
+     \r
+     \r
+          /* Check to see if the memory allocation succeeded */\r
+          if (x == NULL)\r
+          {\r
+               printf("\nUnable to allocate a 1000 element integer\r
+     table");\r
+               exit(0);\r
+          }\r
+     \r
+          /* Assign values to each table element */\r
+          for(n = 0; n < 1000; n++)\r
+          {\r
+               *x = n;\r
+               x++;\r
+          }\r
+     \r
+          /* Return x to the start of the table */\r
+          x -= 1000;\r
+     \r
+          /* Display the values in the table */\r
+          for(n = 0; n < 1000; n++){\r
+               printf("\nElement %d holds a value of %d",n,*x);\r
+               x++;\r
+          }\r
+          /* Deallocate the block of memory now it's no longer required\r
+     */\r
+          free(x);\r
+     }\r
+     \r
+Pointers are also extensively used with character arrays, called strings.\r
+Since all C program strings are terminated by a zero byte we can count\r
+the letters in a string using a pointer;\r
+\r
+     #include <stdio.h>\r
+     #include <string.h>\r
+     \r
+     main()\r
+     {\r
+          char *p;\r
+          char text[100];\r
+          int len;\r
+     \r
+          /* Initialise variable 'text' with some writing */\r
+          strcpy(text,"This is a string of data");\r
+     \r
+          /* Set variable p to the start of variable text */\r
+          p = text;\r
+     \r
+          /* Initialise variable len to zero */\r
+          len = 0;\r
+     \r
+          /* Count the characters in variable text */\r
+          while(*p)\r
+          {\r
+               len++;\r
+               p++;\r
+          }\r
+     \r
+          /* Display the result */\r
+          printf("\nThe string of data has %d characters in it",len);\r
+     }\r
+     \r
+\r
+The 8088/8086 group of CPUs, as used in the IBM PC, divide memory into\r
+64K segments. To address all 1Mb of memory a 20 bit number is used\r
+comprised of an `offset' to and a 64K `segment'. The IBM PC uses special\r
+registers called "segment registers" to record the segments of addresses.\r
+\r
+This leads the C language on the IBM PC to have three new keywords; near,\r
+far and huge.\r
+\r
+near pointers are 16 bits wide and access only data within the current\r
+segment.\r
+\r
+far pointers are comprised of an offset and a segment address allowing\r
+them to access data any where in memory, but arithmetic with far pointers\r
+is fraught with danger! When a value is added or subtracted from a far\r
+pointer it is only actualy the segment part of the pointer that is\r
+affected, thus leading to the situation where a far pointer being\r
+incremented wraps around on its own offset 64K segment.\r
+\r
+huge pointers are a variation on the far pointer that can be successfuly\r
+incremented and decremented through the entire 1Mb range since the\r
+compiler generates code to amend the offset as applicable.\r
+\r
+It will come as no surprise that code using near pointers is executed\r
+faster than code using far pointers, which in turn is faster than code\r
+using huge pointers.\r
+\r
+to give a literal address to a far pointer IBM PC C compilers provide a\r
+macro MK-FP() that has the prototype;\r
+\r
+\r
+     void far *MK_FP(unsigned segment, unsigned offset);\r
+\r
+For example, to create a far pointer to the start of video memory on a\r
+colour IBM PC system you could use;\r
+\r
+          screen = MK_FP(0xB800,0);\r
+\r
+Two coressponding macros provided are;\r
+\r
+FP_SEG() and FP_OFF()\r
+\r
+Which return the segment and offset respectively of a far pointer. The\r
+following example uses FP_SEG() and FP_OFF() to send segment and offset\r
+addresses to a DOS call to create a new directory path;\r
+\r
+\r
+     #include <dos.h>\r
+     \r
+     int makedir(char *path)\r
+     {\r
+          /* Make sub directory, returning non zero on success */\r
+     \r
+          union REGS inreg,outreg;\r
+          struct SREGS segreg;\r
+     \r
+          inreg.h.ah = 0x39;\r
+          segreg.ds = FP_SEG(path);\r
+          inreg.x.dx = FP_OFF(path);\r
+          intdosx(&inreg,&outreg,&segreg);\r
+     \r
+          return(1 - outreg.x.cflag);\r
+     }\r
+     \r
+\r
+Finally, the CPU's segment registers can be read with the function\r
+`segread()'. This is a function unique to C compilers for the the 8086\r
+family of processors. segread() has the function prototype:\r
+\r
+\r
+     void segread(struct SREGS *segp);\r
+\r
+It returns the current values of the segment registers in the SREGS type\r
+structure pointed to by the pointer `segp'. For example:\r
+\r
+     #include <dos.h>\r
+     \r
+     main()\r
+     {\r
+          struct SREGS sregs;\r
+     \r
+          segread(&sregs);\r
+          printf("\nCode segment is currently at %04X, Data segment is at\r
+     %04X",sregs.cs,sregs.ds);\r
+          printf("\nExtra segment is at %04X, Stack segment is at\r
+     %04X",sregs.es,sregs.ss);\r
+     }\r
+                                    \r
+                               STRUCTURES\r
+\r
+\r
+C provides the means to group together variables under one name providing\r
+a convenient means of keeping related information together and providing\r
+a structured approach to data.\r
+\r
+The general form for a structure definition is;\r
+\r
+\r
+     typedef struct\r
+     {\r
+          variable_type variable_name;\r
+          variable_type variable_name;\r
+     }\r
+     structure_name;\r
+\r
+\r
+When accessing data files, with a fixed record structure, the use of a\r
+structure variable becomes essential. The following example shows a\r
+record structure for a very simple name and address file. It declares a\r
+data structure called `data', and comprised of six fields; `name',\r
+`address', `town', `county' , `post' and `telephone'.\r
+\r
+\r
+     typedef struct\r
+     {\r
+          char name[30];\r
+          char address[30];\r
+          char town[30];\r
+          char county[30];\r
+          char post[12];\r
+          char telephone[15];\r
+     }\r
+     data;\r
+\r
+The structure 'data' may then be used in the program as a variable data\r
+type for declaring variables;\r
+\r
+     data record;\r
+\r
+Thus declares a variable called 'record' to be of type 'data'.\r
+\r
+The individual fields of the structure variable are accessed by the\r
+general form;\r
+\r
+     structure_variable.field_name;\r
+\r
+Thus, the name field of the structure variable record is accessed with;\r
+\r
+     record.name\r
+\r
+There is no limit to the number of fields that may comprise a structure,\r
+nor do the fields have to be of the same types, for example;\r
+\r
+\r
+     typedef struct\r
+     {\r
+          char name[30];\r
+          int age;\r
+          char *notes;\r
+     }\r
+     dp;\r
+\r
+Declares a structure 'dp' that is comprised of a character array field,\r
+an integer field and a character pointer field.\r
+\r
+A structure variable may be passed as a parameter by passing the address\r
+of the variable as the parameter with the & operator thus;\r
+\r
+     func(&my_struct)\r
+\r
+\r
+The following is an example program that makes use of a structure to\r
+provide the basic access to the data in a simple name and address file;\r
+\r
+\r
+/* A VERY simple address book written in ANSI C */\r
+\r
+#include <stdio.h>\r
+#include <stdlib.h>\r
+#include <io.h>\r
+#include <string.h>\r
+#include <fcntl.h>\r
+#include <sys\stat.h>\r
+\r
+/* num_lines is the number of screen display lines */\r
+#define num_lines   25\r
+\r
+\r
+typedef struct\r
+{\r
+     char name[30];\r
+     char address[30];\r
+     char town[30];\r
+     char county[30];\r
+     char post[12];\r
+     char telephone[15];\r
+}\r
+data;\r
+\r
+data record;\r
+int handle;\r
+\r
+\r
+/* Function prototypes */\r
+\r
+void ADD_REC(void);\r
+void CLS(void);\r
+void DISPDATA(void);\r
+void FATAL(char *);\r
+void GETDATA(void);\r
+void MENU(void);\r
+void OPENDATA(void);\r
+int SEARCH(void);\r
+\r
+void CLS()\r
+{\r
+     int n;\r
+\r
+     for(n = 0; n < num_lines; n++)\r
+          puts("");\r
+}\r
+\r
+void FATAL(char *error)\r
+{\r
+     printf("\nFATAL ERROR: %s",error);\r
+     exit(0);\r
+}\r
+\r
+void OPENDATA()\r
+{\r
+     /* Check for existence of data file and if not create it */\r
+     /* otherwise open it for reading/writing at end of file */\r
+\r
+     handle = open("address.dat",O_RDWR|O_APPEND,S_IWRITE);\r
+\r
+     if (handle == -1)\r
+     {\r
+          handle = open("address.dat",O_RDWR|O_CREAT,S_IWRITE);\r
+          if (handle == -1)\r
+               FATAL("Unable to create data file");\r
+     }\r
+}\r
+\r
+void GETDATA()\r
+{\r
+     /* Get address data from operator */\r
+\r
+     CLS();\r
+\r
+     printf("Name ");\r
+     gets(record.name);\r
+     printf("\nAddress ");\r
+     gets(record.address);\r
+     printf("\nTown ");\r
+     gets(record.town);\r
+     printf("\nCounty ");\r
+     gets(record.county);\r
+     printf("\nPost Code ");\r
+     gets(record.post);\r
+     printf("\nTelephone ");\r
+     gets(record.telephone);\r
+}\r
+\r
+void DISPDATA()\r
+{\r
+     /* Display address data */\r
+     char text[5];\r
+\r
+     CLS();\r
+\r
+     printf("Name %s",record.name);\r
+     printf("\nAddress %s",record.address);\r
+     printf("\nTown %s",record.town);\r
+     printf("\nCounty %s",record.county);\r
+     printf("\nPost Code %s",record.post);\r
+     printf("\nTelephone %s\n\n",record.telephone);\r
+\r
+     puts("Press RETURN to continue");\r
+     gets(text);\r
+}\r
+\r
+void ADD_REC()\r
+{\r
+     /* Insert or append a new record to the data file */\r
+     int result;\r
+\r
+     result = write(handle,&record,sizeof(data));\r
+\r
+     if (result == -1)\r
+          FATAL("Unable to write to data file");\r
+}\r
+\r
+int SEARCH()\r
+{\r
+     char text[100];\r
+     int result;\r
+\r
+     printf("Enter data to search for ");\r
+     gets(text);\r
+     if (*text == 0)\r
+          return(-1);\r
+\r
+     /* Locate start of file */\r
+     lseek(handle,0,SEEK_SET);\r
+\r
+     do\r
+     {\r
+          /* Read record into memory */\r
+          result = read(handle,&record,sizeof(data));\r
+          if (result > 0)\r
+          {\r
+               /* Scan record for matching data */\r
+               if (strstr(record.name,text) != NULL)\r
+                    return(1);\r
+               if (strstr(record.address,text) != NULL)\r
+                    return(1);\r
+               if (strstr(record.town,text) != NULL)\r
+                    return(1);\r
+               if (strstr(record.county,text) != NULL)\r
+                    return(1);\r
+               if (strstr(record.post,text) != NULL)\r
+                    return(1);\r
+               if (strstr(record.telephone,text) != NULL)\r
+                    return(1);\r
+          }\r
+     }\r
+     while(result > 0);\r
+     return(0);\r
+}\r
+\r
+void MENU()\r
+{\r
+     int option;\r
+     char text[10];\r
+\r
+     do\r
+     {\r
+          CLS();\r
+          puts("\n\t\t\tSelect Option");\r
+          puts("\n\n\t\t\t1 Add new record");\r
+          puts("\n\n\t\t\t2 Search for data");\r
+          puts("\n\n\t\t\t3 Exit");\r
+          puts("\n\n\n\n\n");\r
+          gets(text);\r
+          option = atoi(text);\r
+\r
+          switch(option)\r
+          {\r
+               case 1 : GETDATA();\r
+                     /* Go to end of file to append new record */\r
+                     lseek(handle,0,SEEK_END);\r
+                     ADD_REC();\r
+                     break;\r
+\r
+               case 2 : if (SEARCH())\r
+                          DISPDATA();\r
+                     else\r
+                     {\r
+                          puts("NOT FOUND!");\r
+                          puts("Press RETURN to continue");\r
+                          gets(text);\r
+                     }\r
+                     break;\r
+\r
+               case 3 : break;\r
+          }\r
+     }\r
+     while(option != 3);\r
+}\r
+\r
+void main()\r
+{\r
+     CLS();\r
+     OPENDATA();\r
+     MENU();\r
+}\r
+\r
+Bit Fields\r
+\r
+C allows the inclusion of variables with a size of less than eight bits\r
+to be included in structures. These variables are known as bit fields,\r
+and may be any declared size from one bit upwards.\r
+\r
+The general form for declaring a bit field is;\r
+\r
+\r
+     type name : number_of_bits;\r
+\r
+\r
+For example, to declare a set of status flags, each occupying one bit;\r
+\r
+typedef struct\r
+{\r
+     unsigned carry  : 1;\r
+     unsigned zero   : 1;\r
+     unsigned over   : 1;\r
+     unsigned parity : 1;\r
+}\r
+df;\r
+\r
+df flags;\r
+\r
+The variable `flags' then occupies only four bits in memory, and yet is\r
+comprised of four variables that may be accessed like any other structure\r
+field.\r
+\r
+\r
+\r
+Uunions\r
+\r
+Another facility provided by C for efficient use of available memory is\r
+the union structure. A union structure is a collection of variables that\r
+all share the same memory storage address. As such only one of the\r
+variables is ever accessible at a time.\r
+\r
+The general form of a union definition is;\r
+\r
+\r
+     union name\r
+     {\r
+          type variable_name;\r
+          type variable_name;\r
+          .\r
+          .\r
+          .\r
+          type variable_name;\r
+     };\r
+\r
+Thus, to declare a union structure for two integer variables;\r
+\r
+     union data\r
+     {\r
+          int vara;\r
+          int varb;\r
+     };\r
+\r
+and to declare a variable of type 'data';\r
+\r
+     data my_var;\r
+\r
+The variable 'my_var' is then comprised of the two variables 'vara' and\r
+'varb'  that are accessed like with any form of structure, eg;\r
+\r
+     my_var.vara = 5;\r
+\r
+Assigns a value of 5 to the variable 'vara' of union 'my_var'.\r
+\r
+\r
+Enumerations\r
+\r
+An enumeration assigns ascending integer values to a list of symbols. An\r
+enumeration declaration takes the general form;\r
+\r
+\r
+     enum name { enumeration list } variable_list;\r
+\r
+Thus to define a symbol list of colours, you can say;\r
+\r
+     enum COLOURS\r
+     {\r
+          BLACK,\r
+          BLUE,\r
+          GREEN,\r
+          CYAN,\r
+          RED,\r
+          MAGENTA,\r
+          BROWN,\r
+          LIGHTGRAY,\r
+          DARKGRAY,\r
+          LIGHTBLUE,\r
+          LIGHTGREEN,\r
+          LIGHTCYAN,\r
+          LIGHTRED,\r
+          LIGHTMAGENTA,\r
+          YELLOW,\r
+          WHITE\r
+     };\r
+\r
+Then, the number zero may be referred to by the symbol BLACK, the number\r
+one by the symbol BLUE, the number two by the symbol GREEN and so on.\r
+\r
+The following program illustrates the use of an enumeration list to\r
+symbolise integers;\r
+\r
+     #include <stdio.h>\r
+     \r
+     enum COLOURS\r
+     {\r
+          BLACK,\r
+          BLUE,\r
+          GREEN,\r
+          CYAN,\r
+          RED,\r
+          MAGENTA,\r
+          BROWN,\r
+          LIGHTGRAY,\r
+          DARKGRAY,\r
+          LIGHTBLUE,\r
+          LIGHTGREEN,\r
+          LIGHTCYAN,\r
+          LIGHTRED,\r
+          LIGHTMAGENTA,\r
+          YELLOW,\r
+          WHITE\r
+     };\r
+     \r
+     \r
+     void main()\r
+     {\r
+          int x;\r
+     \r
+          x = RED;\r
+     \r
+          printf("\nVariable 'x' holds %d",x);\r
+     \r
+     }\r
+                                    \r
+                                FILE I/O\r
+\r
+\r
+C provides buffered file streams for file access. Some C platforms, such\r
+as Unix and DOS provide unbuffered file handles as well.\r
+\r
+\r
+\r
+Buffered streams\r
+\r
+Buffered streams are accessed through a variable of type 'file pointer'.\r
+The data type FILE is defined in the header file stdio.h. Thus to declare\r
+a file pointer;\r
+\r
+     #include <stdio.h>\r
+\r
+     FILE *ptr;\r
+\r
+To open a stream C provides the function fopen(), which accepts two\r
+parameters, the name of the file to be opened, and the access mode for\r
+the file to be opened with. The access mode may be any one of;\r
+\r
+\r
+Mode   Description\r
+       \r
+r      Open for reading\r
+w      Create for writing, destroying any\r
+       existing file\r
+a      Open for append, creating a new file\r
+       if it doesn't\r
+        exist\r
+r+      Open an existing file for reading\r
+       and writing\r
+w+      Create for reading and writing,\r
+       destroying any\r
+        existing file\r
+a+      Open for append, creating a new file\r
+       if it doesn't exist.\r
+\r
+\r
+Optionaly either `b' or `t' may be appended for binary or text mode. If\r
+neither is appended, then the file stream will be opened in the mode\r
+described by the global variable _fmode. Data read or written from file\r
+streams opened in text mode undergoes conversion, that is the characters\r
+CR and LF are converted to CR LF pairs on writing, and the CR LF pair is\r
+converted to a single LF on reading. File streams opened in binary mode\r
+do not undergo conversion.\r
+\r
+\r
+If fopen() fails to open the file, it returns a value of NULL (defined in\r
+stdio.h) to the file pointer.\r
+\r
+Thus, the following program will create a new file called "data.txt" and\r
+open it for reading and writing;\r
+\r
+     #include <stdio.h>\r
+     \r
+     void main()\r
+     {\r
+          FILE *fp;\r
+     \r
+          fp = fopen("data.txt","w+");\r
+     \r
+     }\r
+\r
+To close a stream C provides the function fclose(), which accepts the\r
+stream's file pointer as a parameter;\r
+\r
+     fclose(fp);\r
+\r
+If an error occurs in closing the file stream, fclose() returns non zero.\r
+\r
+There are four basic functions for receiving and sending data to and from\r
+streams; fgetc(), fputc(), fgets() and fputs().\r
+\r
+fgetc() simply reads a single character from the specified input stream;\r
+\r
+     char fgetc(FILE *fp);\r
+\r
+Its opposite number is fputc(), which simply writes a single character to\r
+the specified input stream;\r
+\r
+     char fputc(char c, FILE *fp);\r
+\r
+fgets() reads a string from the input stream;\r
+\r
+     char *fgets(char s, int numbytes, FILE *fp);\r
+\r
+It stops reading when either numbytes - 1 bytes have been read, or a\r
+newline character is read in. A null terminating byte is appended to the\r
+read string, s. If an error occurs, fgets() returns NULL.\r
+\r
+\r
+fputs() writes a null terminated string to a stream;\r
+\r
+     int fputs(char *s, FILE *fp);\r
+\r
+Excepting fgets(), which returns a NULL pointer if an error occurs, all\r
+the other functions described above return EOF (defined in stdio.h) if an\r
+error occurs during the operation.\r
+\r
+\r
+The following program creates a copy of the file "data.dat" as "data.old"\r
+and illustrates the use of fopen(), fgetc(), fputc() and fclose();\r
+\r
+\r
+     #include <stdio.h>\r
+     \r
+     int main()\r
+     {\r
+          FILE *in;\r
+          FILE *out;\r
+     \r
+          in = fopen("data.dat","r");\r
+     \r
+          if (in == NULL)\r
+          {\r
+               puts("\nUnable to open file data.dat for reading");\r
+               return(0);\r
+          }\r
+     \r
+          out = fopen("data.old","w+");\r
+     \r
+          if (out == NULL)\r
+          {\r
+               puts("\nUnable to create file data.old");\r
+               return(0);\r
+          }\r
+     \r
+          /* Loop reading and writing one byte at a time until end-of-\r
+     file */\r
+          while(!feof(in))\r
+               fputc(fgetc(in),out);\r
+     \r
+          /* Close the file streams */\r
+          fclose(in);\r
+          fclose(out);\r
+     \r
+          return(0);\r
+     }\r
+\r
+Example program using fputs() to copy text from stream stdin (usually\r
+typed in at the keyboard) to a new file called "data.txt".\r
+\r
+     #include <stdio.h>\r
+     \r
+     int main()\r
+     {\r
+          FILE *fp;\r
+          char text[100];\r
+     \r
+          fp = fopen("data.txt","w+");\r
+     \r
+          do\r
+          {\r
+               gets(text);\r
+               fputs(text,fp);\r
+          }\r
+          while(*text);\r
+     \r
+          fclose(fp);\r
+     }\r
+\r
+\r
+Random access using streams\r
+\r
+Random file access for streams is provided for by the fseek() function\r
+that has the following prototype;\r
+\r
+     int fseek(FILE *fp, long numbytes, int fromwhere);\r
+\r
+fseek() repositions a file pointer associated with a stream previously\r
+opened by a call to fopen(). The file pointer is positioned `numbytes'\r
+from the location `fromwhere', which may be the file beginning, the\r
+current file pointer position, or the end of the file, symbolised by the\r
+constants SEEK_SET, SEEK_CUR and SEEK_END respectively. If a call to\r
+fseek() succeeds, a value of zero is returned.\r
+\r
+\r
+Associated with fseek() is ftell(), which reports the current file\r
+pointer position of a stream, and has the following function prototype;\r
+\r
+\r
+     long int ftell(FILE *fp);\r
+\r
+ftell() returns either the position of the file pointer, measured in\r
+bytes from the start of the file, or -1 upon an error occurring.\r
+\r
+\r
+\r
+Handles\r
+\r
+File handles are opened with the open() function that has the prototype;\r
+\r
+     int open(char *filename,int access[,unsigned mode]);\r
+\r
+If open() is successful, the number of the file handle is returned.\r
+Otherwise open() returns -1.\r
+\r
+The access integer is comprised from bitwise oring together of the\r
+symbolic constants declared in fcntl.h. These vary from compiler to\r
+compiler but may be;\r
+\r
+\r
+     O_APPEND       If set, the file pointer will be set to the end of\r
+the\r
+                    file prior to each write.\r
+     O_CREAT        If the file does not exist it is created.\r
+     O_TRUNC        Truncates the existing file to a length of zero\r
+bytes.\r
+     O_EXCL          Used with O_CREAT\r
+     O_BINARY       Opens the file in binary mode\r
+     O_TEXT         Opens file in text mode\r
+\r
+The optional mode parameter is comprised by bitwise oring of the symbolic\r
+constants defined in stat.h. These vary from C compiler to C compiler but\r
+may be;\r
+\r
+     S_IWRITE       Permission to write\r
+     S_IREAD        Permission to read\r
+\r
+\r
+Once a file handle has been assigned with open(), the file may be\r
+accessed with read() and write().\r
+\r
+Read() has the function prototype;\r
+\r
+     int read(int handle, void *buf, unsigned num_bytes);\r
+\r
+It attempts to read 'num_bytes' and returns the number of bytes actually\r
+read from the file handle 'handle', and stores these bytes in the memory\r
+block pointed to by 'buf'.\r
+\r
+Write() is very similar to read() and has the same function prototype and\r
+return values, but writes 'num_bytes' from the memory block pointed to by\r
+'buf'.\r
+\r
+Files opened with open() are closed using close() that has the function\r
+prototype;\r
+\r
+     int close(int handle);\r
+\r
+close() returns zero on success, and -1 if an error occurs trying to\r
+close the handle.\r
+\r
+Random access is provided by lseek(), which is very similar to fseek(),\r
+except that it accepts an integer file handle as the first parameter\r
+rather than a stream FILE pointer.\r
+\r
+This example uses file handles to read data from stdin (usually the\r
+keyboard) and copy the text to a new file called "data.txt".\r
+\r
+     #include <io.h>\r
+     #include <fcntl.h>\r
+     #include <sys\stat.h>\r
+     \r
+     int main()\r
+     {\r
+          int handle;\r
+          char text[100];\r
+     \r
+          handle = open("data.txt",O_RDWR|O_CREAT|O_TRUNC,S_IWRITE);\r
+     \r
+          do\r
+          {\r
+               gets(text);\r
+               write(handle,&text,strlen(text));\r
+          }\r
+          while(*text);\r
+     \r
+          close(handle);\r
+     }\r
+\r
+\r
+\r
+Advanced File I/O\r
+\r
+The ANSI standard on C defines file IO as by way of file streams, and\r
+defines various functions for file access;\r
+\r
+\r
+fopen() has the prototype;\r
+\r
+     FILE *fopen(const char *name,const char *mode);\r
+\r
+fopen() attempts to open a stream to a file name in a specified mode. If\r
+successful a FILE type pointer is returned to the file stream, if the\r
+call fails NULL is returned. The mode string can be one of the following;\r
+\r
+\r
+Mode      Description\r
+ r        Open for reading only\r
+ w        Create for writing, overwriting any existing file with the same\r
+          name.\r
+ a        Open for append (writing at end of file) or create the file if\r
+it\r
+          does not exist.\r
+ r+       Open an existing file for reading and writing.\r
+ w+       Create a new file for reading and writing.\r
+ a+       Open for append with read and write access.\r
+\r
+\r
+fclose() is used to close a file stream previously opened by a call to\r
+fopen(). It has the prototype;\r
+\r
+     int fclose(FILE *fp);\r
+\r
+When a call to fclose() is successful, all buffers to the stream are\r
+flushed and a value of zero is returned. If the call fails fclose()\r
+returns EOF.\r
+\r
+Many host computers, and the IBM PC is no exception, use buffered file\r
+access, that is when writing to a file stream the data is stored in\r
+memory and only written to the stream when it exceeds a predefined number\r
+of bytes. A power failure occurring before the data has been written to\r
+the stream will result in the data never being written, so the function\r
+fflush() can be called to force all pending data to be written. fflush()\r
+has the prototype;\r
+\r
+     int fflush(FILE *fp);\r
+\r
+When a call to fflush() is successful, the buffers connected with the\r
+stream are flushed and a value of zero is returned. On failure fflush()\r
+returns EOF.\r
+\r
+The location of the file pointer connected with a stream can be\r
+determined with the function ftell(). ftell() has the prototype;\r
+\r
+\r
+     long int ftell(FILE *fp);\r
+\r
+and returns the offset of the file pointer in bytes from the start of the\r
+file, or -1L if the call fails.\r
+\r
+Similarly, you can move the file pointer to a new position with fseek().\r
+fseek() has the prototype;\r
+\r
+     int fseek(FILE *fp, long offset, int from_what_place);\r
+\r
+fseek() attempts to move the file pointer, 'fp' 'offset' bytes from the\r
+position 'from_what_place'. 'from_what_place' is predefined as one of;\r
+\r
+     SEEK_SET       The file's beginning\r
+     SEEK_CUR       The file pointer's current position\r
+     SEEK_END       End of file\r
+\r
+The offset may be a positive value to move the file pointer on through\r
+the file, or negative to move backwards.\r
+\r
+To move a file pointer quickly back to the start of a file, and clear any\r
+references to errors that have occurred C provides the function rewind()\r
+that has the prototype;\r
+\r
+     void rewind(FILE *fp);\r
+\r
+rewind(fp) is similar to fseek(fp,0L,SEEK_SET) in that they both set the\r
+file pointer to the start of the file, but whereas fseek() clears the EOF\r
+error marker, rewind() clears all error indicators.\r
+\r
+Errors occurring with file functions can be checked with the function\r
+ferror() that has the prototype;\r
+\r
+     int ferror(FILE *fp);\r
+\r
+ferror() returns a nonzero value if an error has occurred on the\r
+specified stream. After checking ferror() and reporting any errors you\r
+should clear the error indicators, and this can be done by a call to\r
+clearerr() that has the prototype;\r
+\r
+     void clearerr(FILE *fp);\r
+\r
+The condition of reaching end of file (EOF) can be tested for with the\r
+predefined macro feof() that has the prototype;\r
+\r
+     int feof(FILE *fp);\r
+\r
+feof() returns a nonzero value if an end of file error indicator was\r
+detected on the specified file stream, and zero if the end of file has\r
+not yet been reached.\r
+\r
+Reading data from a file stream can be achieved using several functions;\r
+A single character can be read with fgetc() that has the prototype;\r
+\r
+\r
+     int fgetc(FILE *fp);\r
+\r
+fgetc() returns either the character read converted to an integer, or EOF\r
+if an error occurred.\r
+\r
+Reading a string of data is achieved with fgets(). fgets() attempts to\r
+read a string terminated by a newline character and of no more than a\r
+specified number of bytes from the file stream. It has the prototype;\r
+\r
+\r
+     char *fgets(char s, int n, FILE *fp);\r
+\r
+A successful call to fgets() results in a string being stored in `s' that\r
+is either terminated by a newline character, or that is `n' - 1\r
+characters long, which ever came first. The newline character is retained\r
+by fgets(), and a null bytes is appended to the string. If the call fails\r
+a NULL pointer is returned.\r
+\r
+\r
+Strings may be written to a stream using fputs() that has the prototype;\r
+\r
+     int fputs(const char *s,FILE *fp);\r
+\r
+fputs() writes all the characters in the string `s' to the stream `fp'\r
+except the null terminating byte. On success, fputs() returns the last\r
+character written, on failure it returns EOF.\r
+\r
+To write a single character to a stream use fputc() that has the\r
+prototype;\r
+\r
+     int fputc(int c,FILE *fp);\r
+\r
+If successful, fputc() returns the character written, otherwise it\r
+returns EOF.\r
+\r
+To read a large block of data, or a record from a stream you can use\r
+fread() that has the prototype;\r
+\r
+     size_t fread(void *ptr,size_t size, size_t n, FILE *fp);\r
+\r
+fread() attempts to read 'n' items, each of length 'size' from the file\r
+stream 'fp' into the block of memory pointed to by 'ptr'. To check the\r
+success or failure status of fread() use ferror().\r
+\r
+The sister function to fread() is fwrite() that has the prototype;\r
+\r
+     size_t fwrite(const void *ptr,size_t size, size_t n,FILE *fp);\r
+\r
+that writes 'n' items each of length 'size' from the memory area pointed\r
+to by 'ptr' to the specified stream 'fp'.\r
+\r
+Formatted input from a stream is achieved with fscanf() that has the\r
+prototype;\r
+\r
+     int fscanf(FILE *fp, const char *format[,address ...]);\r
+\r
+fscanf() returns the number of fields successfully stored, and EOF on end\r
+of file. This short example shows how fscanf() is quite useful for\r
+reading numbers from a stream, but hopeless when it comes to strings!\r
+\r
+     #include <stdio.h>\r
+     \r
+     void main()\r
+     {\r
+          FILE *fp;\r
+          int a;\r
+          int b;\r
+          int c;\r
+          int d;\r
+          int e;\r
+          char text[100];\r
+     \r
+          fp = fopen("data.txt","w+");\r
+     \r
+          if(!fp)\r
+          {\r
+               perror("Unable to create file");\r
+               exit(0);\r
+          }\r
+     \r
+          fprintf(fp,"1 2 3 4 5 \"A line of numbers\"");\r
+     \r
+          fflush(fp);\r
+     \r
+          if (ferror(fp))\r
+          {\r
+               fputs("Error flushing stream",stderr);\r
+               exit(1);\r
+          }\r
+     \r
+          rewind(fp);\r
+          if (ferror(fp))\r
+          {\r
+               fputs("Error rewind stream",stderr);\r
+               exit(1);\r
+          }\r
+     \r
+          fscanf(fp,"%d %d %d %d %d %s",&a,&b,&c,&d,&e,text);\r
+          if (ferror(fp))\r
+          {\r
+               fputs("Error reading from stream",stderr);\r
+               exit(1);\r
+          }\r
+     \r
+          printf("\nfscanf() returned %d %d %d %d %d %s",a,b,c,d,e,text);\r
+     }\r
+     \r
+As you can see from the example, fprintf() can be used to write formatted\r
+data to a stream.\r
+\r
+If you wish to store the position of a file pointer on a stream, and then\r
+later restore it to the same position you can use the functions fgetpos()\r
+and fsetpos(). fgetpos() reads the current location of the file pointer\r
+and has the prototype;\r
+\r
+     int fgetpos(FILE *fp, fpos_t *pos);\r
+\r
+fsetpos() repositions the file pointer and has the prototype;\r
+\r
+     int fsetpos(FILE *fp, const fpos_t *fpos);\r
+\r
+fpos_t is defined in stdio.h.\r
+\r
+These functions are more convenient than doing an ftell() followed by an\r
+fseek().\r
+\r
+An open stream can have a new file associated with it in place of the\r
+existing file by using the function freopen() that has the prototype;\r
+\r
+     FILE *freopen(const char *name,const char *mode,FILE *fp);\r
+\r
+freopen() closes the existing stream and then attempts to reopens it with\r
+the specified file name. This is useful for redirecting the predefined\r
+streams stdin, stdout and stderr to a file or device.\r
+\r
+For example; if you wish to redirect all output intended to stdout\r
+(usually the host computer's display device) to a printer you might use;\r
+\r
+     freopen("LPT1","w",stdout);\r
+\r
+where LPT1 is the name of the printer device (on a PC host, LPT1 is the\r
+name of the parallel port).\r
+\r
+\r
+Predefined I/O Streams\r
+\r
+There are three predefined I/O streams; stdin, stdout, and stderr. The\r
+streams stdin and stdout default to the keyboard and display\r
+respectively, but can be redirected on some hardware platforms, such as\r
+the PC and under UNIX. The stream stderr defaults to the display and is\r
+not usually redirected by the operator. Thus it can be used for the\r
+display of error messages even when program output has been redirected,\r
+such as with;\r
+\r
+      fputs("Error message",stderr);\r
+\r
+The functions printf() and puts(), output data to the stream stdout, and\r
+can therefore be redirected by the operator of the program. scanf() and\r
+gets() accept input from the stream stdin.\r
+\r
+As an example of file i/o with the PC consider the following short\r
+program that does a hex dump of a specified file to the predefined stream\r
+stdout, which may be redirected to a file using;\r
+\r
+          dump filename.ext > target.ext\r
+\r
+     #include <stdio.h>\r
+     #include <fcntl.h>\r
+     #include <io.h>\r
+     #include <string.h>\r
+     \r
+     main(int argc, char *argv[])\r
+     {\r
+          unsigned counter;\r
+          unsigned char v1[20];\r
+          int f1;\r
+          int x;\r
+          int n;\r
+     \r
+          if (argc != 2)\r
+          {\r
+               fputs("\nERROR: Syntax is dump f1\n",stderr);\r
+               return(1);\r
+          }\r
+     \r
+          f1 = open(argv[1],O_RDONLY);\r
+     \r
+          if (f1 == -1)\r
+          {\r
+               fprintf(stderr,"\nERROR: Unable to open %s\n",argv[1]);\r
+               return(1);\r
+          }\r
+     \r
+          fprintf(stdout,"\nDUMP OF FILE %s\n\n",strupr(argv[1]));\r
+     \r
+          counter = 0;\r
+     \r
+          while(1)\r
+          {\r
+               /* Set buffer to zero bytes */\r
+               memset(v1,0,20);\r
+     \r
+               /* Read buffer from file */\r
+               x = _read(f1,&v1,16);\r
+     \r
+               /* x will be 0 on EOF or -1 on error */\r
+               if (x < 1)\r
+                    break;\r
+     \r
+               /* Print file offset to stdout */\r
+               fprintf(stdout,"%06d(%05x) ",counter,counter);\r
+     \r
+               counter += 16;\r
+     \r
+               /* print hex values of buffer to stdout */\r
+               for(n = 0; n < 16; n++)\r
+                    fprintf(stdout,"%02x ",v1[n]);\r
+     \r
+               /* Print ascii values of buffer to stdout */\r
+               for(n = 0; n < 16; n++)\r
+               {\r
+                    if ((v1[n] > 31) && (v1[n] < 128))\r
+                         fprintf(stdout,"%c",v1[n]);\r
+                    else\r
+                         fputs(".",stdout);\r
+               }\r
+     \r
+               /* Finish the line with a new line */\r
+               fputs("\n",stdout);\r
+          }\r
+     \r
+          /* successful termination */\r
+          return(0);\r
+     }\r
+                                    \r
+                                 STRINGS\r
+\r
+The C language has one of the most powerful string handling capabilities\r
+of any general purpose computer language.\r
+\r
+A string is a single dimension array of characters terminated by a zero\r
+byte.\r
+\r
+Strings may be initialised in two ways. Either in the source code where\r
+they may be assigned a constant value, as in;\r
+\r
+     int main()\r
+     {\r
+          char *p = "System 5";\r
+          char name[] = "Test Program" ;\r
+     }\r
+\r
+or at run time by the function strcpy() that has the function prototype;\r
+\r
+     char *strcpy(char *destination, char *source);\r
+\r
+strcpy() copies the string pointed to by source into the location pointed\r
+to by destination as in the following example;\r
+\r
+\r
+     #include<stdio.h>\r
+     \r
+     int main()\r
+     {\r
+          char name[50];\r
+     \r
+          strcpy(name,"Servile Software");\r
+     \r
+          printf("\nName equals %s",name);\r
+     }\r
+\r
+C also allows direct access to each individual byte of the string, so the\r
+following is quite permissible;\r
+\r
+     #include<stdio.h>\r
+     \r
+     int main()\r
+     {\r
+          char name[50];\r
+     \r
+          strcpy(name,"Servile Software");\r
+     \r
+          printf("\nName equals %s",name);\r
+     \r
+          /* Replace first byte with lower case 's' */\r
+          name[0] = 's';\r
+     \r
+          printf("\nName equals %s",name);\r
+     }\r
+     \r
+The ANSI standard on the C programming language defines the following\r
+functions for use with strings;\r
+\r
+char *strcat(char *dest, char *source)            Appends string source\r
+to the end of string destination.\r
+\r
+char *strchr(char *s, int c)            Returns a pointer to the first\r
+occurence of character 'c' within s.\r
+\r
+int strcmp(char *s1, char *s2)               Compares strings s1 and s2\r
+returning        < 0 if s1 is less than s2\r
+                                                       == 0 if s1 and s2\r
+are the same\r
+                                                       > 0 if s1 is\r
+greater than s2\r
+\r
+int strcoll(char *s1, char *s2)              Compares strings s1 and s2\r
+                    according to the collating sequence set by\r
+                              setlocale() returning    < 0 if s1 is less\r
+than s2\r
+                                             == 0 if s1 and s2 are the\r
+same\r
+                                             > 0 if s1 is greater than s2\r
+\r
+char *strcpy(char *dest, char *src)          Copies string src into\r
+string dest.\r
+\r
+unsigned strcspn(char *s1, char *s2)         Returns the length of string\r
+s1 that consists entirely of characters not in\r
+string s2.\r
+\r
+unsigned strlen(char *s)                Returns the length of string s.\r
+\r
+char *strncat(char *dest, char *src, unsigned len)     Copies at most\r
+'len' characters from string src into string dest.\r
+\r
+int strncmp(char *s1, char *s2, unsigned len)     Compares at most 'len'\r
+characters from\r
+                              string s1 with string s2 returning      < 0\r
+if s1 is less than s2\r
+                                                  == 0 if s1 and s2 are\r
+the same\r
+                                                  > 0 if s1 is greater\r
+than s2\r
+\r
+char *strncpy(char *dest, char *src, unsigned len)     Copies 'len'\r
+characters from string  src into string dest, truncating or\r
+                              padding with zero bytes as required.\r
+\r
+char *strpbrk(char *s1, char *s2)            Returns a pointer to the\r
+first character in string s1 that occurs in\r
+                              string s2.\r
+\r
+char *strrchr(char *s, int c)           Returns a pointer to the last\r
+occurence of 'c' within string s.\r
+\r
+unsigned strspn(char *s1, char *s2)          Returns the length of the\r
+initial segment of string s1 that consists\r
+                              entirely of characters in string s2.\r
+\r
+char *strstr(char *s1, char *s2)             Returns a pointer to the\r
+first occurence of string s2 within string\r
+                              s1, or NULL if string s2 is not found in\r
+string s1.\r
+\r
+char *strtok(char *s1, char *s2)             Returns a pointer to the\r
+token found in string s1 that is defined by\r
+                              delimiters in string s2. Returns NULLif no\r
+tokens are found.\r
+\r
+The ANSI standard also defines various functions for converting strings\r
+into numbers and numbers into strings.\r
+\r
+Some C compilers include functions to convert strings to upper and lower\r
+case, but these functions are not defined in the ANSI standard. However,\r
+the ANSI standard does define the functions; toupper() and tolower() that\r
+return an\r
+\r
+integer parameter converted to upper and lowercase respectively. By using\r
+these functions we can create our own ANSI compatible versions;\r
+\r
+\r
+     #include<stdio.h>\r
+     \r
+     void strupr(char *source)\r
+     {\r
+          char *p;\r
+     \r
+          p = source;\r
+          while(*p)\r
+          {\r
+               *p = toupper(*p);\r
+               p++;\r
+          }\r
+     }\r
+     \r
+     void strlwr(char *source)\r
+     {\r
+          char *p;\r
+     \r
+          p = source;\r
+          while(*p)\r
+          {\r
+               *p = tolower(*p);\r
+               p++;\r
+          }\r
+     }\r
+\r
+\r
+     int main()\r
+     {\r
+          char name[50];\r
+     \r
+          strcpy(name,"Servile Software");\r
+     \r
+          printf("\nName equals %s",name);\r
+     \r
+          strupr(name);\r
+     \r
+          printf("\nName equals %s",name);\r
+     \r
+          strlwr(name);\r
+     \r
+          printf("\nName equals %s",name);\r
+     }\r
+\r
+C does not impose a maximum length that a string may be, unlike other\r
+computer languages. However, some CPUs impose restrictions on the maximum\r
+size a block of memory can be. For example, the 8088 family of CPUs, as\r
+used by the IBM PC, impose a limit of 64K bytes on a segment of memory.\r
+\r
+An example program to reverse all the characters in a string.\r
+\r
+     #include <stdio.h>\r
+     #include <string.h>\r
+     \r
+     char *strrev(char *s)\r
+     {\r
+          /* Reverses the order of all characters in a string except the\r
+     null */\r
+          /* terminating byte */\r
+     \r
+          char *start;\r
+          char *end;\r
+          char tmp;\r
+     \r
+          /* Set pointer 'end' to last character in string */\r
+          end = s + strlen(s) - 1;\r
+     \r
+          /* Preserve pointer to start of string */\r
+          start = s;\r
+     \r
+          /* Swop characters */\r
+          while(end >= s)\r
+          {\r
+               tmp = *end;\r
+               *end = *s;\r
+               *s = tmp;\r
+               end--;\r
+               s++;\r
+          }\r
+          return(start);\r
+     }\r
+     \r
+     \r
+     main()\r
+     {\r
+          char text[100];\r
+          char *p;\r
+     \r
+          strcpy(text,"This is a string of data");\r
+     \r
+          p = strrev(text);\r
+     \r
+          printf("\n%s",p);\r
+     }\r
+     \r
+\r
+Strtok()\r
+\r
+The function strtok() is a very powerful standard C feature for\r
+extracting substrings from within a single string. It is used where the\r
+substrings are separated by known delimiters, such as commas in the\r
+following example;\r
+\r
+     #include <stdio.h>\r
+     #include <string.h>\r
+     \r
+     main()\r
+     {\r
+          char data[50];\r
+          char *p;\r
+     \r
+          strcpy(data,"RED,ORANGE,YELLOW,GREEN,BLUE,INDIGO,VIOLET");\r
+     \r
+          p = strtok(data,",");\r
+          while(p)\r
+          {\r
+               puts(p);\r
+               p = strtok(NULL,",");\r
+          };\r
+     }\r
+     \r
+Or this program can be written with a for() loop thus;\r
+\r
+     #include <stdio.h>\r
+     #include <string.h>\r
+     \r
+     main()\r
+     {\r
+          char data[50];\r
+          char *p;\r
+     \r
+          strcpy(data,"RED,ORANGE,YELLOW,GREEN,BLUE,INDIGO,VIOLET");\r
+     \r
+          for(strtok(data,","); p; p = strtok(NULL,","))\r
+          {\r
+               puts(p);\r
+          };\r
+     }\r
+     \r
+They both compile to the same code but follow different programming\r
+styles.\r
+\r
+Initially, you call strtok() with the name of the string variable to be\r
+parsed, and a second string that contains the known delimiters. Strtok()\r
+then returns a pointer to the start of the first substring and replaces\r
+the first token with a zero delimiter. Subsequent calls to strtok() can\r
+be made in a loop passing NULL as the string to be parsed, and strtok()\r
+will return the subsequent substrings.\r
+\r
+\r
+Since strtok() can accept many delimiter characters in the second\r
+parameter string we can use it as the basis of a simple word counting\r
+program;\r
+\r
+     #include <stdio.h>\r
+     #include <stdlib.h>\r
+     #include <string.h>\r
+     \r
+     void main(int argc, char *argv[])\r
+     {\r
+          FILE *fp;\r
+          char buffer[256];\r
+          char *p;\r
+          long count;\r
+     \r
+          if (argc != 2)\r
+          {\r
+               fputs("\nERROR: Usage is wordcnt <file>\n",stderr);\r
+               exit(0);\r
+          }\r
+     \r
+          /* Open file for reading */\r
+          fp = fopen(argv[1],"r");\r
+     \r
+          /* Check the open was okay */\r
+          if (!fp)\r
+          {\r
+               fputs("\nERROR: Cannot open source file\n",stderr);\r
+               exit(0);\r
+          }\r
+     \r
+          /* Initialise word count */\r
+          count = 0;\r
+     \r
+          do\r
+          {\r
+               /* Read a line of data from the file */\r
+               fgets(buffer,255,fp);\r
+     \r
+               /* check for an error in the read or EOF */\r
+               if (ferror(fp) || feof(fp))\r
+                    continue;\r
+     \r
+               /* count words in received line */\r
+               /* Words are defined as separated by the characters */\r
+               /* \t(tab) \n(newline) , ; : . ! ? ( ) - and [space] */\r
+               p = strtok(buffer,"\t\n,;:.!?()- ");\r
+               while(p)\r
+               {\r
+                    count++;\r
+                    p = strtok(NULL,"\t\n,;:.!?()- ");\r
+               }\r
+          }\r
+          while(!ferror(fp) && !feof(fp));\r
+     \r
+          /* Finished reading. Was it due to an error? */\r
+          if (ferror(fp))\r
+          {\r
+               fputs("\nERROR: Reading source file\n",stderr);\r
+               fclose(fp);\r
+               exit(0);\r
+          }\r
+     \r
+          /* Reading finished due to EOF, quite valid so print count */\r
+          printf("\nFile %s contains %ld words\n",argv[1],count);\r
+          fclose(fp);\r
+     }\r
+     \r
+\r
+\r
+\r
+Converting Numbers To And From Strings\r
+\r
+All C compilers provide a facility for converting numbers to strings.\r
+This being sprintf(). However, as happens sprintf() is a multi-purpose\r
+function that is therefore large and slow. The following function ITOS()\r
+accepts two parameters, the first being a signed integer and the second\r
+being a pointer to a character string. It then copies the integer into\r
+the memory pointed to by the character pointer. As with sprintf() ITOS()\r
+does not check that the target string is long enough to accept the result\r
+of the conversion. You should then ensure that the target string is long\r
+enough.\r
+\r
+Example function for copying a signed integer into a string;\r
+\r
+     void ITOS(long x, char *ptr)\r
+     {\r
+          /* Convert a signed decimal integer to a string */\r
+     \r
+          long pt[9] = { 100000000, 10000000, 1000000, 100000, 10000,\r
+     1000, 100, 10, 1 };\r
+          int n;\r
+     \r
+          /* Check sign */\r
+          if (x < 0)\r
+          {\r
+               *ptr++ = '-';\r
+               /* Convert x to absolute */\r
+               x = 0 - x;\r
+          }\r
+     \r
+          for(n = 0; n < 9; n++)\r
+          {\r
+               if (x > pt[n])\r
+               {\r
+                    *ptr++ = '0' + x / pt[n];\r
+                    x %= pt[n];\r
+               }\r
+          }\r
+          return;\r
+     }\r
+     \r
+To convert a string to a floating point number, C provides two functions;\r
+atof() and strtod(). atof() has the prototype;\r
+\r
+     double atof(const char *s);\r
+\r
+strtod has the prototype;\r
+\r
+     double strtod(const char *s,char **endptr);\r
+\r
+Both functions scan the string and convert it as far as they can, until\r
+they come across a character they don't understand. The difference\r
+between the two functions is that if strtod() is passed a character\r
+pointer for parameter `endptr', it sets that pointer to the first\r
+character in the string that terminated the conversion. Because of its\r
+better error reporting, by way of endptr, strtod() is often preferred to\r
+atof().\r
+\r
+To convert a string to an integer use atoi() that has the prototype;\r
+\r
+\r
+     int atoi(const char *s);\r
+\r
+atoi() does not check for an overflow, and the results are undefined!\r
+\r
+atol() is a similar function but returns a long. Alternatively, you can\r
+use strtol() and stroul() instead that have better error checking.\r
+\r
+                                    \r
+                              TEXT HANDLING\r
+\r
+Human languages write information down as `text'. This is comprised of\r
+words, figures and punctuation. The words being made up of upper case and\r
+lower case letters. Processing text with a computer is a commonly\r
+required task, and yet quite a difficult one.\r
+\r
+The ANSI C definitions include string processing functions that are by\r
+their nature sensitive to case. That is the letter `A' is seen as\r
+distinct from the letter `a'. This is the first problem that must be\r
+overcome by the programmer. Fortunately both Borland's Turbo C compilers\r
+and Microsoft's C compilers include case insensitive forms of the string\r
+functions.\r
+\r
+stricmp() for example is the case insensitive form of strcmp(), and\r
+strnicmp() is the case insensitive form of strncmp().\r
+\r
+If you are concerned about writing portable code, then you must restrict\r
+yourself to the ANSI C functions, and write your own case insensitive\r
+functions using the tools provided.\r
+\r
+Here is a simple implementation of a case insensitive version of\r
+strstr().  The function simply makes a copy of the parameter strings,\r
+converts those copies both to upper case and then does a standard\r
+strstr() on the copies.  The offset of the target string within the\r
+source string will be the same for the copy as the original, and so it\r
+can be returned relative to the parameter string.\r
+\r
+\r
+     char *stristr(char *s1, char *s2)\r
+     {\r
+          char c1[1000];\r
+          char c2[1000];\r
+          char *p;\r
+     \r
+          strcpy(c1,s1);\r
+          strcpy(c2,s2);\r
+     \r
+          strupr(c1);\r
+          strupr(c2);\r
+     \r
+          p = strstr(c1,c2);\r
+          if (p)\r
+               return s1 + (p - c1);\r
+          return NULL;\r
+     }\r
+     \r
+This function scans a string, s1 looking for the word held in s2. The\r
+word must be a complete word, not simply a character pattern, for the\r
+function to return true. It makes use of the stristr() function described\r
+previously.\r
+\r
+     int word_in(char *s1,char *s2)\r
+     {\r
+          /* return non-zero if s2 occurs as a word in s1 */\r
+          char *p;\r
+          char *q;\r
+          int ok;\r
+     \r
+          ok = 0;\r
+          q = s1;\r
+     \r
+          do\r
+          {\r
+               /* Locate character occurence s2 in s1 */\r
+               p = stristr(q,s2);\r
+               if (p)\r
+               {\r
+                    /* Found */\r
+                    ok = 1;\r
+     \r
+                    if (p > s1)\r
+                    {\r
+                         /* Check previous character */\r
+                         if (*(p - 1) >= 'A' && *(p - 1) <= 'z')\r
+                              ok = 0;\r
+                    }\r
+     \r
+                    /* Move p to end of character set */\r
+                    p += strlen(s2);\r
+                    if (*p)\r
+                    {\r
+                         /* Check character following */\r
+                         if (*p >= 'A' && *p <= 'z')\r
+                              ok = 0;\r
+                    }\r
+               }\r
+               q = p;\r
+          }\r
+          while(p && !ok);\r
+          return ok;\r
+     }\r
+\r
+\r
+Some more useful functions for dealing with text are truncstr() that\r
+truncates a string;\r
+\r
+     void truncstr(char *p,int num)\r
+     {\r
+          /* Truncate string by losing last num characters */\r
+          if (num < strlen(p))\r
+               p[strlen(p) - num] = 0;\r
+     }\r
+     \r
+trim() that removes trailing spaces from the end of a string;\r
+\r
+     void trim(char *text)\r
+     {\r
+          /* remove trailing spaces */\r
+          char *p;\r
+     \r
+          p = &text[strlen(text) - 1];\r
+          while(*p == 32 && p >= text)\r
+               *p-- = 0;\r
+     }\r
+     \r
+strlench() that changes the length of a string by adding or deleting\r
+characters;\r
+\r
+     void strlench(char *p,int num)\r
+     {\r
+          /* Change length of string by adding or deleting characters */\r
+     \r
+          if (num > 0)\r
+               memmove(p + num,p,strlen(p) + 1);\r
+          else\r
+          {\r
+               num = 0 - num;\r
+               memmove(p,p + num,strlen(p) + 1);\r
+          }\r
+     }\r
+     \r
+strins() that inserts a string into another string;\r
+\r
+     void strins(char *p, char *q)\r
+     {\r
+          /* Insert string q into p */\r
+          strlench(p,strlen(q));\r
+          strncpy(p,q,strlen(q));\r
+     }\r
+     \r
+strchg() that replaces all occurences of one sub-string with another\r
+within a target string;\r
+\r
+     void strchg(char *data, char *s1, char *s2)\r
+     {\r
+          /* Replace all occurences of s1 with s2 */\r
+          char *p;\r
+          char changed;\r
+     \r
+          do\r
+          {\r
+               changed = 0;\r
+               p = strstr(data,s1);\r
+               if (p)\r
+               {\r
+                    /* Delete original string */\r
+                    strlench(p,0 - strlen(s1));\r
+     \r
+                    /* Insert replacement string */\r
+                    strins(p,s2);\r
+                    changed = 1;\r
+               }\r
+          }\r
+          while(changed);\r
+     }\r
+                                    \r
+                                  TIME\r
+\r
+C provides a function, time(), which reads the computer's system clock to\r
+return the system time as a number of seconds since midnight on January\r
+the first, 1970. However, this value can be converted to a useful string\r
+by the function ctime() as illustrated in the following example;\r
+\r
+     #include <stdio.h>\r
+     #include <time.h>\r
+     \r
+     int main()\r
+     {\r
+          /* Structure to hold time, as defined in time.h  */\r
+          time_t t;\r
+     \r
+          /* Get system date and time from computer */\r
+          t = time(NULL);\r
+          printf("Today's date and time: %s\n",ctime(&t));\r
+     }\r
+     \r
+The string returned by ctime() is comprised of seven fields;\r
+\r
+     Day of the week,\r
+     Month of the year,\r
+     Date of the day of the month,\r
+     hour,\r
+     minutes,\r
+     seconds,\r
+     century of the year\r
+\r
+terminated by a newline character and null terminating byte. Since the\r
+fields always occupy the same width, slicing operations can be carried\r
+out on the string with ease. The following program defines a structure\r
+`time' and a function gettime() that extracts the hours, minutes and\r
+seconds of the current time and places them in the structure;\r
+\r
+\r
+     #include <stdio.h>\r
+     #include <time.h>\r
+     \r
+     struct time\r
+     {\r
+          int ti_min;         /* Minutes */\r
+          int ti_hour;        /* Hours */\r
+          int ti_sec;         /* Seconds */\r
+     };\r
+     \r
+     void gettime(struct time *now)\r
+     {\r
+          time_t t;\r
+          char temp[26];\r
+          char *ts;\r
+     \r
+          /* Get system date and time from computer */\r
+          t = time(NULL);\r
+     \r
+          /* Translate dat and time into a string */\r
+          strcpy(temp,ctime(&t));\r
+     \r
+          /* Copy out just time part of string */\r
+          temp[19] = 0;\r
+          ts = &temp[11];\r
+     \r
+          /* Scan time string and copy into time structure */\r
+          sscanf(ts,"%2d:%2d:%2d",&now->ti_hour,&now->ti_min,&now-\r
+     >ti_sec);\r
+     }\r
+     \r
+     int main()\r
+     {\r
+          struct time now;\r
+     \r
+          gettime(&now);\r
+     \r
+          printf("\nThe time is\r
+     %02d:%02d:%02d",now.ti_hour,now.ti_min,now.ti_sec);\r
+     \r
+     }\r
+\r
+The ANSI standard on C does actually provide a function ready made to\r
+convert the value returned by time() into a structure;\r
+\r
+     #include <stdio.h>\r
+     #include <time.h>\r
+     \r
+     int main()\r
+     {\r
+          time_t t;\r
+          struct tm *tb;\r
+     \r
+          /* Get time into t */\r
+          t = time(NULL);\r
+     \r
+          /* Convert time value t into structure pointed to by tb */\r
+          tb = localtime(&t);\r
+     \r
+          printf("\nTime is %02d:%02d:%02d",tb->tm_hour,tb->tm_min,tb-\r
+     >tm_sec);\r
+     }\r
+     \r
+The structure 'tm' is defined in time.h as;\r
+\r
+     struct tm\r
+     {\r
+          int tm_sec;\r
+          int tm_min;\r
+          int tm_hour;\r
+          int tm_mday;\r
+          int tm_mon;\r
+          int tm_year;\r
+          int tm_wday;\r
+          int tm_yday;\r
+          int tm_isdst;\r
+     };\r
+\r
+\r
+Timers\r
+Often a program must determine the date and time from the host computer's\r
+non-volatile RAM. There are several time functions provided by the ANSI\r
+standard on C that allow a program to retrieve, from the host computer,\r
+the current date and time;\r
+\r
+time() returns the number of seconds that have elapsed since midnight on\r
+January the 1st 1970. It has the prototype;\r
+\r
+     time_t time(time_t *timer);\r
+\r
+time() fills in the time_t variable sent as a parameter and returns the\r
+same value. You can call time() with a NULL parameter and just collect\r
+the return value thus;\r
+\r
+     #include <time.h>\r
+     \r
+     void main()\r
+     {\r
+          time_t now;\r
+     \r
+          now = time(NULL);\r
+     }\r
+     \r
+asctime() converts a time block to a twenty six character string of the\r
+format;\r
+\r
+                Wed Oct 14 10:23:45 1992\n\0\r
+\r
+asctime() has the prototype;\r
+\r
+               char *asctime(const struct tm *tblock);\r
+\r
+ctime() converts a time value (as returned by time()) into a twenty six\r
+chracter string of the same format as asctime(). For example;\r
+\r
+     #include <stdio.h>\r
+     #include <time.h>\r
+     \r
+     void main()\r
+     {\r
+          time_t now;\r
+          char date[30];\r
+     \r
+          now = time(NULL);\r
+          strcpy(date,ctime(&now));\r
+     }\r
+     \r
+difftime() returns the difference, in seconds, between two values (as\r
+returned by time()). This can be useful for testing the elapsed time\r
+between two events, the time a function takes to execute, and for\r
+creating consistent delays that are irrelevant of the host computer.\r
+\r
+An example delay program;\r
+\r
+     #include <stdio.h>\r
+     #include <time.h>\r
+     \r
+     \r
+     void DELAY(int period)\r
+     {\r
+          time_t start;\r
+     \r
+          start = time(NULL);\r
+          while(time(NULL) < start + period)\r
+               ;\r
+     }\r
+     \r
+     void main()\r
+     {\r
+          printf("\nStarting delay now....(please wait 5 seconds)");\r
+     \r
+          DELAY(5);\r
+     \r
+          puts("\nOkay, I've finished!");\r
+     }\r
+\r
+gmtime() converts a local time value (as returned by time()) to the GMT\r
+time and stores it in a time block. This function depends upon the global\r
+variable timezone being set.\r
+\r
+\r
+The time block is a predefined structure (declared in time.h) as follows;\r
+\r
+     struct tm\r
+     {\r
+          int tm_sec;\r
+          int tm_min;\r
+          int tm_hour;\r
+          int tm_mday;\r
+          int tm_mon;\r
+          int tm_year;\r
+          int tm_wday;\r
+          int tm_yday;\r
+          int tm_isdst;\r
+     };\r
+\r
+tm_mday records the day of the month, ranging from 1 to 31; tm_wday is\r
+the day of the week with Sunday being represented by 0; the year is\r
+recorded less 1900; tm_isdst is a flag to show whether daylight saving\r
+time is in effect. The actual names of the structure and its elements may\r
+vary from compiler to compiler, but the structure should be the same in\r
+essence.\r
+\r
+mktime() converts a time block to a calendar format. It follows the\r
+prototype;\r
+\r
+                time_t mktime(struct tm *t);\r
+\r
+The following example allows entry of a date, and uses mktime() to\r
+calculate the day of the week appropriate to that date. Only dates from\r
+the first of January 1970 are recognisable by the time functions.\r
+\r
+     #include <stdio.h>\r
+     #include <time.h>\r
+     #include <string.h>\r
+     \r
+     void main()\r
+     {\r
+          struct tm tsruct;\r
+          int okay;\r
+          char data[100];\r
+          char *p;\r
+          char *wday[] = {"Sunday", "Monday", "Tuesday", "Wednesday",\r
+     "Thursday", "Friday", "Saturday" ,\r
+                    "prior to 1970, thus not known" };\r
+          do\r
+          {\r
+               okay = 0;\r
+               printf("\nEnter a date as dd/mm/yy ");\r
+               p = fgets(data,8,stdin);\r
+               p = strtok(data,"/");\r
+     \r
+               if (p != NULL)\r
+                    tsruct.tm_mday = atoi(p);\r
+               else\r
+                    continue;\r
+     \r
+               p = strtok(NULL,"/");\r
+               if (p != NULL)\r
+                    tsruct.tm_mon = atoi(p);\r
+               else\r
+                    continue;\r
+     \r
+               p = strtok(NULL,"/");\r
+     \r
+               if (p != NULL)\r
+                    tsruct.tm_year = atoi(p);\r
+               else\r
+                    continue;\r
+               okay = 1;\r
+          }\r
+          while(!okay);\r
+     \r
+          tsruct.tm_hour = 0;\r
+          tsruct.tm_min = 0;\r
+          tsruct.tm_sec = 1;\r
+          tsruct.tm_isdst = -1;\r
+     \r
+          /* Now get day of the week */\r
+          if (mktime(&tsruct) == -1)\r
+          tsruct.tm_wday = 7;\r
+     \r
+          printf("That was %s\n",wday[tsruct.tm_wday]);\r
+     }\r
+     \r
+mktime() also makes the neccessary adjustments for values out of range,\r
+this can be utilised for discovering what the date will be in n number of\r
+days time thus;\r
+\r
+\r
+     #include <stdio.h>\r
+     #include <time.h>\r
+     #include <string.h>\r
+     \r
+     void main()\r
+     {\r
+          struct tm *tsruct;\r
+          time_t today;\r
+     \r
+          today = time(NULL);\r
+          tsruct = localtime(&today);\r
+     \r
+          tsruct->tm_mday += 10;\r
+          mktime(tsruct);\r
+     \r
+          printf("In ten days it will be %02d/%02d/%2d\n", tsruct-\r
+     >tm_mday,tsruct->tm_mon + 1,tsruct->tm_year);\r
+     \r
+     }\r
+     \r
+\r
+This program uses Julian Dates to decide any day of the week since the\r
+1st of October 1582 when the Gregorian calendar was introduced.\r
+\r
+     char *WDAY(int day, int month, int year)\r
+     {\r
+          /* Returns a pointer to a string representing the day of the\r
+     week */\r
+     \r
+          static char *cday[] = { "Saturday","Sunday","Monday","Tuesday",\r
+     "Wednesday","Thursday","Friday" };\r
+          double yy;\r
+          double yt;\r
+          double j;\r
+          int y1;\r
+          int y4;\r
+          int x;\r
+     \r
+          yy = year / 100;\r
+          y1 = (int)(yy);\r
+          yt = year / 400;\r
+          y4 = (int)(yt);\r
+          x = 0;\r
+     \r
+          if (month < 3)\r
+          {\r
+               year--;\r
+               x = 12;\r
+          }\r
+     \r
+          j = day + (int)(365.25*year);\r
+     \r
+          j += (int)(30.6001 * (month + 1 + x)) - y1 + y4;\r
+     \r
+          if (yy == y1 && yt != y4 && month < 3)\r
+               j++;\r
+     \r
+          j = 1 + j - 7 * (int)(j/7);\r
+     \r
+          if (j > 6)\r
+               j -= 7;\r
+     \r
+          return(cday[j]);\r
+     }\r
+     \r
+     \r
+With time() and difftime() we can create a timer for testing the\r
+execution times of functions thus;\r
+\r
+     #include <stdio.h>\r
+     #include <time.h>\r
+     \r
+     main()\r
+     {\r
+          time_t now;\r
+          time_t then;\r
+          double elapsed;\r
+     \r
+          int n;\r
+     \r
+          now = time(NULL);\r
+     \r
+          /* This loop is adjustable for multiple passes */\r
+          for(n = 0; n < 5000; n++)\r
+               /* Call the function to test */\r
+               func();\r
+     \r
+          then = time(NULL);\r
+     \r
+          elapsed = difftime(then,now);\r
+          printf("\nElapsed seconds==%lf\n",elapsed);\r
+     }\r
+     \r
+By way of time() and ctime() the current system date and time can be\r
+retrieved from the host computer thus;\r
+\r
+     #include <stdio.h>\r
+     #include <time.h>\r
+     \r
+     main()\r
+     {\r
+          time_t now;\r
+          char *date;\r
+          int n;\r
+     \r
+          /* Get system time */\r
+          now = time(NULL);\r
+     \r
+          /* Convert system time to a string */\r
+          date = ctime(&now);\r
+     \r
+          /*Display system time */\r
+          printf("\nIt is %s",date);\r
+     }\r
+     \r
+time_t is a type defined in time.h as the type of variable returned by\r
+time(). This type may vary from compiler to compiler, and therefore is\r
+represented by the type "time_t".\r
+                                    \r
+                              HEADER FILES\r
+\r
+Function prototypes for library functions supplied with the C compiler,\r
+and standard macros are declared in header files.\r
+\r
+The ANSI standard on the C programming language lists the following\r
+header files;\r
+\r
+Header file    Description\r
+               \r
+assert.h       Defines the assert debugging macro\r
+ctype.h        Character classification and\r
+               conversion macros\r
+errno.h        Constant mnemonics for error codes\r
+float.h        Defines implementation specific\r
+               macros for dealing with floating\r
+               point mathematics\r
+limits.h       Defines implementation specific\r
+               limits on type values\r
+locale.h       Country specific parameters\r
+math.h         Prototypes for mathematics functions\r
+setjmp.h       Defines typedef and functions for\r
+               setjmp/longjmp\r
+signal.h       Constants and declarations for use by\r
+               signal() and raise()\r
+stdarg.h       Macros for dealing with argument\r
+               lists\r
+stddef.h       Common data types and macros\r
+stdio.h        Types and macros required for\r
+               standard I/O\r
+stdlib.h       Prototypes of commonly used functions\r
+               and miscellany\r
+string.h       String manipulation function\r
+               prototypes\r
+time.h         Structures for time conversion\r
+               routines\r
+                                    \r
+                                DEBUGGING\r
+\r
+The ANSI standard on C includes a macro function for debugging called\r
+assert(). This expands to an if() statement, which if it returns true\r
+terminates the program and outputs to the standard error stream a message\r
+comprised of:\r
+\r
+\r
+     Assertion failed: <test>, file <module>, line <line number>\r
+     Abnormal program termination\r
+For example, the following program accidentally assigns a zero value to a\r
+pointer!\r
+\r
+     #include <stdio.h>\r
+     #include <assert.h>\r
+     \r
+     main()\r
+     {\r
+          /* Demonstration of assert */\r
+     \r
+          int *ptr;\r
+          int x;\r
+     \r
+          x = 0;\r
+     \r
+          /* Whoops! error in this line! */\r
+          ptr = x;\r
+     \r
+          assert(ptr != NULL);\r
+     }\r
+     \r
+When run, this program terminates with the message:\r
+\r
+     Assertion failed: ptr != 0, file TEST.C, line 16\r
+     Abnormal program termination\r
+\r
+When a program is running okay, the assert() functions can be removed\r
+from the compiled program by simply adding the line;\r
+\r
+     #define NDEBUG\r
+\r
+before the #include <assert.h> line. Effectively the assert functions are\r
+commented out in the preprocessed source before compilation, this means\r
+that the assert expressions are not evaluated, and thus cannot cause any\r
+side effects.\r
+                                    \r
+                               FLOAT ERRORS\r
+\r
+Floating point numbers are decimal fractions, decimal fractions do not\r
+accurately equate to normal fractions as not every number will divide\r
+precisely by ten. This creates the potential for rounding errors in\r
+calculations that use floating point numbers. The following program\r
+illustrates one such example of rounding error problems;\r
+\r
+\r
+     #include <stdio.h>\r
+     \r
+     void main()\r
+     {\r
+          float number;\r
+     \r
+          for(number = 1; number > 0.4; number -= 0.01)\r
+               printf("\n%f",number);\r
+     }\r
+     \r
+At about 0.47 (depending upon the host computer and compiler) the program\r
+starts to store an inaccurate value for 'number'.\r
+\r
+This problem can be minimised by using longer floating point numbers,\r
+doubles or long doubles that have larger storage space allocated to them.\r
+For really accurate work though, you should use integers and only convert\r
+to a floating point number for display. You also should notice that most\r
+C compilers default floating point numbers to `doubles' and when using\r
+`float' types have to convert the double down to a float!\r
+\r
+\r
+                                    \r
+                             ERROR HANDLING\r
+\r
+When a system error occurs within a program, for example when an attempt\r
+to open a file fails, it is helpful to the program's user to display a\r
+message reporting the failure. Equally it is useful to the program's\r
+developer to know why the error occurred, or at least as much about it as\r
+possible. To this end the ANSI standard on C describes a function,\r
+perror(), which has the prototype;\r
+\r
+\r
+     void perror(const char *s);\r
+\r
+and is used to display an error message. The program's own prefixed error\r
+message is passed to perror() as the string parameter. This error message\r
+is displayed by perror() followed by the host's system error separated by\r
+a colon. The following example illustrates a use for perror();\r
+\r
+\r
+     #include <stdio.h>\r
+     \r
+     void main()\r
+     {\r
+          FILE *fp;\r
+          char fname[] = "none.xyz";\r
+     \r
+          fp = fopen(fname,"r");\r
+     \r
+          if(!fp)\r
+               perror(fname);\r
+          return;\r
+     }\r
+     \r
+If the fopen() operation fails, a message similar to;\r
+\r
+     none.xyz: No such file or directory\r
+\r
+is displayed.\r
+\r
+You should note, perror() sends its output to the predefined stream\r
+`stderr', which is usually the host computer's display unit.\r
+\r
+\r
+perror() finds its message from the host computer via the global variable\r
+'errno' that is set by most, but not all system functions.\r
+\r
+Unpleasant errors might justify the use of abort(). abort() is a function\r
+that terminates the running program with a message;\r
+\r
+     "Abnormal program termination"\r
+\r
+and returns an exit code of 3 to the parent process or operating system.\r
+\r
+\r
+\r
+Critical Error Handling With The IBM PC AND DOS\r
+\r
+The IBM PC DOS operating system provides a user amendable critical error\r
+handling function. This function is usually discovered by attempting to\r
+write to a disk drive that does not have a disk in it, in which case the\r
+familiar;\r
+\r
+     Not ready error writing drive A\r
+     Abort Retry Ignore?\r
+\r
+Message is displayed on the screen. Fine when it occurs from within a DOS\r
+program, not so fine from within your own program!\r
+\r
+The following example program shows how to redirect the DOS critical\r
+error interrupt to your own function;\r
+\r
+\r
+     /* DOS critical error handler test */\r
+     \r
+     #include <stdio.h>\r
+     #include <dos.h>\r
+     \r
+     void interrupt new_int();\r
+     void interrupt (*old_int)();\r
+     \r
+     char status;\r
+     \r
+     main()\r
+     {\r
+          FILE *fp;\r
+     \r
+          old_int = getvect(0x24);\r
+     \r
+          /* Set critical error handler to my function */\r
+          setvect(0x24,new_int);\r
+     \r
+          /* Generate an error by not having a disc in drive A */\r
+          fp = fopen("a:\\data.txt","w+");\r
+     \r
+          /* Display error status returned */\r
+          printf("\nStatus ==  %d",status);\r
+     \r
+     }\r
+     \r
+     void interrupt new_int()\r
+     {\r
+          /* set global error code */\r
+          status = _DI;\r
+     \r
+          /* ignore error and return */\r
+          _AL = 0;\r
+     }\r
+     \r
+When the DOS critical error interrupt is called, a status message is\r
+passed in the low byte of the DI register. This message is one of;\r
+\r
+Code                     Meaning\r
+                         \r
+00                       Write-protect error\r
+01                       Unknown unit\r
+02                       Drive not ready\r
+03                       Unknown command\r
+04                       Data error, bad CRC\r
+05                       Bad request structure\r
+                         length\r
+06                       Seek error\r
+07                       Unknown media type\r
+08                       Sector not found\r
+09                       Printer out of paper\r
+0A                       Write error\r
+0B                       Read error\r
+0C                       General failure\r
+\r
+Your critical error interrupt handler can transfer this status message\r
+into a global variable, and then set the result message held in register\r
+AL to one of;\r
+\r
+\r
+Code                     Action\r
+                         \r
+00                       Ignore error\r
+01                       Retry\r
+02                       Terminate program\r
+03                       Fail (Available with\r
+                         DOS 3.3 and above)\r
+\r
+\r
+If you choose to set AL to 02, terminate program, you should ensure ALL\r
+files are closed first since DOS will terminate the program abruptly,\r
+leaving files open and memory allocated, not a pretty state to be in!\r
+\r
+\r
+The example program shown returns an ignore status from the critical\r
+error interrupt, and leaves the checking of any errors to the program\r
+itself. So, in this example after the call to fopen() we could check the\r
+return value in fp, and if it reveals an error (NULL in this case) we\r
+could then check the global variable status and act accordingly, perhaps\r
+displaying a polite message to the user to put a disk in the floppy drive\r
+and ensure that the door is closed.\r
+\r
+The following is a practical function for checking whether a specified\r
+disc drive can be accessed. It should be used with the earlier critical\r
+error handler and global variable `status'.\r
+\r
+     int DISCOK(int drive)\r
+     {\r
+          /* Checks for whether a disc can be read */\r
+          /* Returns false (zero) on error */\r
+          /* Thus if(!DISCOK(drive)) */\r
+          /*          error();  */\r
+     \r
+          unsigned char buffer[25];\r
+     \r
+          /* Assume okay */\r
+          status = 0;\r
+     \r
+          /* If already logged to disc, return okay */\r
+          if ('A' + drive == diry[0])\r
+               return(1);\r
+     \r
+          /* Attempt to read disc */\r
+          memset(buffer,0,20);\r
+          sprintf(buffer,"%c:$$$.$$$",'A'+drive);\r
+     \r
+          _open(buffer,O_RDONLY);\r
+     \r
+          /* Check critical error handler status */\r
+          if (status == 0)\r
+               return(1);\r
+     \r
+          /* Disc cannot be read */\r
+          return(0);\r
+     }\r
+                                    \r
+                                  CAST\r
+\r
+\r
+Casting tells the compiler what a data type is, and can be used to change\r
+a data type. For example, consider the following;\r
+\r
+     #include <stdio.h>\r
+     \r
+     void main()\r
+     {\r
+          int x;\r
+          int y;\r
+     \r
+          x = 10;\r
+          y = 3;\r
+     \r
+          printf("\n%lf",x / y);\r
+     }\r
+\r
+The printf() function has been told to expect a double. However, the\r
+compiler sees the variables `x' and `y' as integers, and an error occurs!\r
+To make this example work we must tell the compiler that the result of\r
+the expression x / y is a double, this is done with a cast thus;\r
+\r
+\r
+     #include <stdio.h>\r
+     \r
+     void main()\r
+     {\r
+          int x;\r
+          int y;\r
+     \r
+          x = 10;\r
+          y = 3;\r
+     \r
+          printf("\n%lf",(double)(x / y));\r
+     }\r
+\r
+Notice the data type `double' is enclosed by parenthesis, and so is the\r
+expression to convert. But now, the compiler knows that the result of the\r
+expression is a double, but it still knows that the variables `x' and `y'\r
+are integers and so an integer division will be carried out. We have to\r
+cast the constants thus;\r
+\r
+     #include <stdio.h>\r
+     \r
+     void main()\r
+     {\r
+          int x;\r
+          int y;\r
+     \r
+          x = 10;\r
+          y = 3;\r
+     \r
+          printf("\n%lf",(double)(x) / (double)(y));\r
+     }\r
+\r
+Because both of the constants are doubles, the compiler knows that the\r
+outcome of the expression will also be a double.\r
+\r
+\r
+\r
+                                    \r
+                      THE IMPORTANCE OF PROTOTYPING\r
+\r
+Prototyping a function involves letting the compiler know in advance what\r
+type of values a function will receive and return. For example, lets look\r
+at strtok(). This has the prototype;\r
+\r
+\r
+     char *strtok(char *s1, const char *s2);\r
+\r
+This prototype tells the compiler that strtok() will return a character\r
+pointer, the first received parameter will be a pointer to a character\r
+string, and that string can be changed by strtok(), and the last\r
+parameter will be a pointer to a character string that strtok() cannot\r
+change.\r
+\r
+The compiler knows how much space to allocate for the return parameter,\r
+sizeof(char *), but without a prototype for the function the compiler\r
+will assume that the return value of strtok() is an integer, and will\r
+allocate space for a return type of int, that is sizeof(int). If an\r
+integer and a character pointer occupy the same number of bytes on the\r
+host computer no major problems will occur, but if a character pointer\r
+occupies more space than an integer, then the compiler wont have\r
+allocated enough space for the return value and the return from a call to\r
+strtok() will overwrite some other bit of memory. If, as so often happens\r
+the return value is returned via the stack, the results of confusing the\r
+compiler can be disastrous!\r
+\r
+Thankfully most C compilers will warn the programmer if a call to a\r
+function has been made without a prototype, so that you can add the\r
+required function prototypes.\r
+\r
+Consider the following example that will not compile on most modern C\r
+compilers due to the nasty error in it;\r
+\r
+\r
+     #include <stdio.h>\r
+     \r
+     int FUNCA(int x, int y)\r
+     {\r
+          return(MULT(x,y));\r
+     }\r
+     \r
+     double MULT(double x, double y)\r
+     {\r
+          return(x * y);\r
+     }\r
+     \r
+     \r
+     main()\r
+     {\r
+          printf("\n%d",FUNCA(5,5));\r
+     }\r
+     \r
+When the compiler first encounters the function MULT() it is as a call\r
+from within FUNCA(). In the absence of any prototype for MULT() the\r
+compiler assumes that MULT() returns an integer. When the compiler finds\r
+the definition for function MULT() it sees that a return of type double\r
+has been declared. The compiler then reports an error in the compilation\r
+saying something like;\r
+\r
+\r
+     "Type mismatch in redclaration of function 'MULT'"\r
+\r
+What the compiler is really trying to say is, prototype your functions\r
+before using them! If this example did compile, and was then run it\r
+probably would crash the computer's stack and cause a system hang.\r
+\r
+                                    \r
+                          POINTERS TO FUNCTIONS\r
+\r
+C allows a pointer to point to the address of a function, and this\r
+pointer to be called rather than specifying the function. This is used by\r
+interrupt changing functions and may be used for indexing functions\r
+rather than using switch statements. For example;\r
+\r
+     #include <stdio.h>\r
+     #include <math.h>\r
+     \r
+     double (*fp[7])(double x);\r
+     \r
+     void main()\r
+     {\r
+          double x;\r
+          int p;\r
+     \r
+          fp[0] = sin;\r
+          fp[1] = cos;\r
+          fp[2] = acos;\r
+          fp[3] = asin;\r
+          fp[4] = tan;\r
+          fp[5] = atan;\r
+          fp[6] = ceil;\r
+     \r
+          p = 4;\r
+     \r
+          x = fp[p](1.5);\r
+          printf("\nResult %lf",x);\r
+     }\r
+\r
+This example program defines an array of pointers to functions, (*fp[])()\r
+that are then called dependant upon the value in the indexing variable p.\r
+This program could also be written;\r
+\r
+     #include <stdio.h>\r
+     #include <math.h>\r
+     \r
+     void main()\r
+     {\r
+          double x;\r
+          int p;\r
+     \r
+          p = 4;\r
+     \r
+          switch(p)\r
+          {\r
+               case 0 :  x = sin(1.5);\r
+                     break;\r
+               case 1 :  x = cos(1.5);\r
+                     break;\r
+               case 2 :  x = acos(1.5);\r
+                     break;\r
+               case 3 :  x = asin(1.5);\r
+                     break;\r
+               case 4 :  x = tan(1.5);\r
+                     break;\r
+               case 5 :  x = atan(1.5);\r
+                     break;\r
+               case 6 :  x = ceil(1.5);\r
+                     break;\r
+          }\r
+          puts("\nResult %lf",x);\r
+     }\r
+\r
+The first example, using pointers to the functions, compiles into much\r
+smaller code and executes faster than the second example.\r
+\r
+The table of pointers to functions is a useful facility when writing\r
+language interpreters, the program compares an entered instruction\r
+against a table of key words that results in an index variable being set\r
+and then the program simply needs to call the function pointer indexed by\r
+the variable, rather than wading through a lengthy switch() statement.\r
+\r
+                                    \r
+                            DANGEROUS PITFALLS\r
+\r
+One of the most dangerous pitfalls can occur with the use of gets(). This\r
+function accepts input from the stream stdin until it receives a newline\r
+character, which it does not pass to the program. All the data it\r
+receives is stored in memory starting at the address of the specified\r
+string, and quite happily overflowing into other variables! This danger\r
+can be avoided by using fgets() that allows a maximum number of\r
+characters to be specified, so you can avoid overflow problems. Notice\r
+though that fgets() does retain the newline character scanf() is another\r
+function best avoided. It accepts input from stdin and stores the\r
+received data at the addresses provided to it. If those addresses are not\r
+really addresses where the data ends up is anybodys guess!\r
+\r
+This example is okay, since scanf() has been told to store the data at\r
+the addresses occupied by the two variables `x' and `y'.\r
+\r
+\r
+     void main()\r
+     {\r
+          int x;\r
+          int y;\r
+     \r
+          scanf("%d%d",&x,&y);\r
+     }\r
+     \r
+But in this example scanf() has been told to store the data at the\r
+addresses suggested by the current values of `x' and `y'! An easy and\r
+common mistake to make, and yet one that can have very peculiar effects.\r
+\r
+\r
+     void main()\r
+     {\r
+          int x;\r
+          int y;\r
+     \r
+          scanf("%d%d",x,y);\r
+     }\r
+     \r
+The answer is, don't use scanf(), use fgets() and parse your string\r
+manually using the standard library functions strtod(), strtol() and\r
+strtoul().\r
+\r
+Here is the basis of a simple input string parser that returns the\r
+individual input fields from an entered string;\r
+\r
+     #include <stdio.h>\r
+     #include <string.h>\r
+     \r
+     void main()\r
+     {\r
+          char input[80];\r
+          char *p;\r
+     \r
+          puts("\nEnter a string ");\r
+          fgets(input,79,stdin);\r
+     \r
+          /* now parse string for input fields */\r
+          puts("The fields entered are:");\r
+          p = strtok(input,", ");\r
+          while(p)\r
+          {\r
+               puts(p);\r
+               p = strtok(NULL,", ");\r
+          }\r
+     }\r
+     \r
+                                    \r
+                                 SIZEOF\r
+\r
+A preprocessor instruction, `sizeof', returns the size of an item, be it\r
+a structure, pointer, string or whatever. However! take care when using\r
+`sizeof'. Consider the following program;\r
+\r
+\r
+     #include <stdio.h>\r
+     #include <mem.h>\r
+     \r
+     char string1[80]; char *text = "This is a string of data" ;\r
+     \r
+     void main()\r
+     {\r
+          /* Initialise string1 correctly */\r
+          memset(string1,0,sizeof(string1));\r
+     \r
+          /* Copy some text into string1 ? */\r
+          memcpy(string1,text,sizeof(text));\r
+     \r
+          /* Display string1 */\r
+          printf("\nString 1 = %s\n",string1);\r
+     }\r
+     \r
+What it is meant to do is initialise all 80 elements of string1 to\r
+zeroes, which it does alright, and then copy the constant string `text'\r
+into the variable `string1'. However, variable text is a pointer, and so\r
+the sizeof(text) instruction returns the size of the character pointer\r
+(perhaps two bytes) rather than the length of the string pointed to by\r
+the pointer!  If the length of the string pointed to by `text' happened\r
+to be the same as the size of a character pointer then no error would be\r
+noticed.\r
+\r
+                                    \r
+                               INTERRUPTS\r
+\r
+The IBM PC BIOS and DOS contain functions that may be called by a program\r
+by way of the function's interrupt number. The address of the function\r
+assigned to each interrupt is recorded in a table in RAM called the\r
+interrupt vector table. By changing the address of an interrupt vector a\r
+program can effectively disable the original interrupt function and\r
+divert any calls to it to its own function. This was done by the critical\r
+error handler described in the section on error handling.\r
+\r
+Borland's Turbo C provides two library functions for reading and changing\r
+an interrupt vector. These are: setvect() and getvect(). The\r
+corresponding Microsoft C library functions are: _dos_getvect() and\r
+_dos_setvect().\r
+\r
+getvect() has the function prototype;\r
+\r
+     void interrupt(*getvect(int interrupt_no))();\r
+\r
+setvect() has the prototype;\r
+\r
+     void setvect(int interrupt_no, void interrupt(*func)());\r
+\r
+To read and save the address of an existing interrupt a program uses\r
+getvect() thus;\r
+\r
+     /* Declare variable to record old interrupt */\r
+     void interrupt(*old)(void);\r
+     \r
+     main()\r
+     {\r
+          /* get old interrupt vector */\r
+          old = getvect(0x1C);\r
+          .\r
+          .\r
+          .\r
+     }\r
+     \r
+Where 0x1C is the interrupt vector to be retrieved.\r
+\r
+To then set the interrupt vector to a new address, our own function, we\r
+use setvect() thus;\r
+\r
+     void interrupt new(void)\r
+     {\r
+          .\r
+          .\r
+          /* New interrupt function */\r
+          .\r
+          .\r
+          .\r
+     }\r
+     \r
+     main()\r
+     {\r
+          .\r
+          .\r
+          .\r
+          setvect(0x1C,new);\r
+          .\r
+          .\r
+          .\r
+          .\r
+     }\r
+     \r
+There are two important points to note about interrupts;\r
+\r
+First, if the interrupt is called by external events then before changing\r
+the vector you MUST disable the interrupt callers using disable() and\r
+then re-enable the interrupts after the vector has been changed using\r
+enable().  If a call is made to the interrupt while the vector is being\r
+changed ANYTHING could happen!\r
+\r
+Secondly, before your program terminates and returns to DOS you must\r
+reset any changed interrupt vectors! The exception to this is the\r
+critical error handler interrupt vector that is restored automatically by\r
+DOS, so your program needn't bother restoring it.\r
+\r
+This example program hooks the PC clock timer interrupt to provide a\r
+background clock process while the rest of the program continues to run.\r
+If included with your own program that requires a constantly displayed\r
+clock on screen, you need only amend the display coordinates in the call\r
+to puttext(). Sincle the closk display code is called by a hardware\r
+issued interrupt, your program can start the clock and forget it until it\r
+terminates.\r
+\r
+     \r
+     /* Compile in LARGE memory model */\r
+     \r
+     #include <stdio.h>\r
+     #include <dos.h>\r
+     #include <time.h>\r
+     #include <conio.h>\r
+     #include <stdlib.h>\r
+     \r
+     enum { FALSE, TRUE };\r
+     \r
+     #define COLOUR (BLUE << 4) | YELLOW\r
+     \r
+     #define BIOS_TIMER  0x1C\r
+     \r
+     static unsigned installed = FALSE;\r
+     static void interrupt (*old_tick) (void);\r
+     \r
+     static void interrupt tick (void)\r
+     {\r
+          int i;\r
+          struct tm *now;\r
+          time_t this_time;\r
+          char time_buf[9];\r
+          static time_t last_time = 0L;\r
+          static char video_buf[20] =\r
+          {\r
+               ' ', COLOUR, '0', COLOUR, '0', COLOUR, ':', COLOUR, '0',\r
+     COLOUR,\r
+               '0', COLOUR, ':', COLOUR, '0', COLOUR, '0', COLOUR, ' ',\r
+     COLOUR\r
+          };\r
+     \r
+          enable ();\r
+     \r
+          if (time (&this_time) != last_time)\r
+          {\r
+               last_time = this_time;\r
+     \r
+               now = localtime(&this_time);\r
+     \r
+               sprintf(time_buf, "%02d:%02d.%02d",now->tm_hour,now-\r
+     >tm_min,now->tm_sec);\r
+     \r
+               for (i = 0; i < 8; i++)\r
+               {\r
+                    video_buf[(i + 1) << 1] = time_buf[i];\r
+               }\r
+     \r
+               puttext (71, 1, 80, 1, video_buf);\r
+          }\r
+     \r
+          old_tick ();\r
+     }\r
+     \r
+     void stop_clock (void)\r
+     {\r
+          if (installed)\r
+          {\r
+               setvect (BIOS_TIMER, old_tick);\r
+               installed = FALSE;\r
+          }\r
+     }\r
+     \r
+     void start_clock (void)\r
+     {\r
+          static unsigned first_time = TRUE;\r
+     \r
+          if (!installed)\r
+          {\r
+               if (first_time)\r
+               {\r
+                    atexit (stop_clock);\r
+                    first_time = FALSE;\r
+               }\r
+     \r
+               old_tick = getvect (BIOS_TIMER);\r
+               setvect (BIOS_TIMER, tick);\r
+               installed = TRUE;\r
+          }\r
+     }\r
+                                    \r
+                                 SIGNAL\r
+\r
+Interrupts raised by the host computer can be trapped and diverted in\r
+several ways. A simple method is to use signal().\r
+\r
+Signal() takes two parameters in the form;\r
+\r
+     void (*signal (int sig, void (*func) (int))) (int);\r
+\r
+The first parameter, `sig' is the signal to be caught. These are often\r
+predefined by the header file `signal.h'.\r
+\r
+The second parameter is a pointer to a function to be called when the\r
+signal is raised. This can either be a user function, or a macro defined\r
+in the header file `signal.h' to do some arbitrary task, such as ignore\r
+the signal for example.\r
+\r
+On a PC platform, it is often useful to disable the `ctrl-break' key\r
+combination that is used to terminate a running program by the user. The\r
+following PC signal() call replaces the predefined signal `SIGINT', which\r
+equates to the ctrl-break interrupt request, with the predefined macro\r
+`SIG-IGN', ignore the request;\r
+\r
+\r
+     signal(SIGINT,SIG_IGN);\r
+\r
+This example catches floating point errors on a PC, and zero divisions!\r
+\r
+     #include <stdio.h>\r
+     #include <signal.h>\r
+     \r
+     void (*old_sig)();\r
+     \r
+     void catch(int sig)\r
+     {\r
+          printf("Catch was called with: %d\n",sig);\r
+     }\r
+     \r
+     \r
+     void main()\r
+     {\r
+          int a;\r
+          int b;\r
+     \r
+          old_sig = signal(SIGFPE,catch);\r
+     \r
+          a = 0;\r
+          b = 10 / a;\r
+     \r
+          /* Restore original handler before exiting! */\r
+          signal(SIGFPE,old_sig);\r
+     }\r
+     \r
+                                    \r
+                          SORTING AND SEARCHING\r
+\r
+The ANSI C standard defines qsort(), a function for sorting a table of\r
+data. The function follows the format;\r
+\r
+     qsort(void *base,size_t elements,size_t width,int (*cmp)(void *,\r
+void *));\r
+\r
+The following short program illustrates the use of qsort() with a\r
+character array.\r
+\r
+     #include <string.h>\r
+     \r
+     main()\r
+     {\r
+          int n;\r
+          char data[10][20];\r
+     \r
+          /* Initialise some arbirary data */\r
+     \r
+          strcpy(data[0],"RED");\r
+          strcpy(data[1],"BLUE");\r
+          strcpy(data[2],"GREEN");\r
+          strcpy(data[3],"YELLOW");\r
+          strcpy(data[4],"INDIGO");\r
+          strcpy(data[5],"BROWN");\r
+          strcpy(data[6],"BLACK");\r
+          strcpy(data[7],"ORANGE");\r
+          strcpy(data[8],"PINK");\r
+          strcpy(data[9],"CYAN");\r
+     \r
+          /* Sort the data table */\r
+          qsort(data[0],10,20,strcmp);\r
+     \r
+          /* Print the data table */\r
+          for(n = 0; n < 10; n++)\r
+               puts(data[n]);\r
+     }\r
+     \r
+\r
+Here is a program that implements the shell sort algorithm (this one is\r
+based on the routine in K & R), which sorts arrays of pointers based upon\r
+the data pointed to by the pointers;\r
+\r
+     #include <stdio.h>\r
+     #include <stdlib.h>\r
+     #include <string.h>\r
+     \r
+     #define LINELEN     80\r
+     #define MAXLINES    2000\r
+     \r
+     char *lines[MAXLINES];\r
+     int lastone;\r
+     \r
+     void SHELL(void);\r
+     \r
+     void SHELL()\r
+     {\r
+          /* SHELL Sort Courtesy of K & R */\r
+     \r
+          int gap;\r
+          int i;\r
+          int j;\r
+          char temp[LINELEN];\r
+     \r
+          for(gap = lastone / 2; gap > 0; gap /= 2)\r
+          for(i = gap; i < lastone; i++)\r
+               for(j = i - gap; j >= 0 && strcmp(lines[j] , lines[j +\r
+     gap]) >\r
+                  0; j -= gap)\r
+               {\r
+                    strcpy(temp,lines[j]);\r
+                    strcpy(lines[j] , lines[j + gap]);\r
+                    strcpy(lines[j + gap] , temp);\r
+     \r
+               }\r
+     }\r
+     \r
+     void main(int argc, char *argv[])\r
+     {\r
+          FILE *fp;\r
+          char buff[100];\r
+          int n;\r
+\r
+          /* Check command line parameter has been given */\r
+          if (argc != 2)\r
+          {\r
+               printf("\nError: Usage is SERVSORT file");\r
+               exit(0);\r
+          }\r
+     \r
+          /* Attempt to open file for updating */\r
+          fp = fopen(argv[1],"r+");\r
+          if (fp == NULL)\r
+          {\r
+               printf("\nError: Unable to open %s",argv[1]);\r
+               exit(0);\r
+          }\r
+     \r
+          /* Initialise element counter to zero */\r
+          lastone = 0;\r
+     \r
+          /* Read file to be sorted */\r
+          while((fgets(buff,100,fp)) != NULL)\r
+          {\r
+               /* Allocate memory block*/\r
+               lines[lastone] = malloc(LINELEN);\r
+               if (lines[lastone] == NULL)\r
+               {\r
+                    printf("\nError: Unable to allocate memory");\r
+                    fclose(fp);\r
+                    exit(0);\r
+               }\r
+               strcpy(lines[lastone],buff);\r
+               lastone++;\r
+     \r
+               if (lastone > MAXLINES)\r
+               {\r
+                    printf("\nError: Too many lines in source file");\r
+                    exit(0);\r
+               }\r
+          }\r
+          /* Call sort function */\r
+          SHELL();\r
+     \r
+          /* Close file */\r
+          fclose(fp);\r
+     \r
+          /* Reopen file in create mode */\r
+          fp = fopen(argv[1],"w+");\r
+     \r
+          /* Copy sorted data from memory to disk */\r
+          for(n = 0; n < lastone; n++)\r
+               fputs(lines[n],fp);\r
+     \r
+          /* Close file finally */\r
+          fclose(fp);\r
+     \r
+          /* Return to calling program */\r
+          return(1);\r
+     }\r
+     \r
+\r
+If we want to use qsort() with a table of pointers we have to be a bit\r
+more clever than usual.\r
+\r
+This example uses the colours again, but this time they are stored in\r
+main memory and indexed by a table of pointers. Because we have a table\r
+of pointers to sort there are two differences between this program's\r
+qsort() and the previous one;\r
+\r
+First we can't use strcmp() as the qsort() comparison function, secondly\r
+the width of the table being sorted is sizeof(char *), that is the size\r
+of a character pointer.\r
+\r
+Notice the comparison function cmp() that receives two parameters, both\r
+are pointers to a pointer. qsort() sends to this function the values held\r
+in data[], which are in turn pointers to the data. So we need to use this\r
+indirection to locate the data, otherwise we would be comparing the\r
+addresses at which the data is held rather than the data itself!\r
+\r
+     #include <alloc.h>\r
+     #include <string.h>\r
+     \r
+     /* Function prototype for comparison function */\r
+     int (cmp)(char **,char **);\r
+     \r
+     int cmp(char **s1, char **s2)\r
+     {\r
+          /* comparison function using pointers to pointers */\r
+          return(strcmp(*s1,*s2));\r
+     }\r
+     \r
+     main()\r
+     {\r
+          int n;\r
+          char *data[10];\r
+     \r
+          for(n = 0; n < 10; n++)\r
+               data[n] = malloc(20);\r
+     \r
+          strcpy(data[0],"RED");\r
+          strcpy(data[1],"BLUE");\r
+          strcpy(data[2],"GREEN");\r
+          strcpy(data[3],"YELLOW");\r
+          strcpy(data[4],"INDIGO");\r
+          strcpy(data[5],"BROWN");\r
+          strcpy(data[6],"BLACK");\r
+          strcpy(data[7],"ORANGE");\r
+          strcpy(data[8],"PINK");\r
+          strcpy(data[9],"CYAN");\r
+     \r
+          /* The data table is comprised of pointers */\r
+          /* so the call to qsort() must reflect this */\r
+          qsort(data,10,sizeof(char *),cmp);\r
+     \r
+          for(n = 0; n < 10; n++)\r
+               puts(data[n]);\r
+     }\r
+     \r
+The quick sort is a fast sorting algorithm that works by subdividing the\r
+data table into two sub-tables and then subdividing the sub-tables. As it\r
+subdivides the table, so it compares the elements in the table and swaps\r
+them as required.\r
+\r
+The following program implements the quick sort algorithm, which is\r
+usually already used by qsort();\r
+\r
+\r
+     #include <string.h>\r
+     \r
+     #define MAXELE  2000\r
+     \r
+     char data[10][20];\r
+     int lastone;\r
+     \r
+     void QKSORT()\r
+     {\r
+          /* Implementation of QUICKSORT algorithm */\r
+     \r
+          int i;\r
+          int j;\r
+          int l;\r
+          int p;\r
+          int r;\r
+          int s;\r
+          char temp[100];\r
+          static int sl[MAXELE][2];\r
+     \r
+          /* sl[] is an index to the sub-table */\r
+     \r
+          l = 0;\r
+          r = lastone;\r
+          p = 0;\r
+     \r
+          do\r
+          {\r
+               while(l < r)\r
+               {\r
+                    i = l;\r
+                    j = r;\r
+                    s = -1;\r
+     \r
+                    while(i < j)\r
+                    {\r
+                         if (strcmp(data[i],data[j]) > 0)\r
+                         {\r
+                              strcpy(temp,data[i]);\r
+                              strcpy(data[i],data[j]);\r
+                              strcpy(data[j],temp);\r
+                              s = 0 - s;\r
+                         }\r
+     \r
+                         if (s == 1)\r
+                              i++;\r
+                         else\r
+                              j--;\r
+                    }\r
+     \r
+                    if (i + 1 < r)\r
+                    {\r
+                         p++;\r
+                         sl[p][0] = i + 1;\r
+                         sl[p][1] = r;\r
+                    }\r
+                    r = i - 1;\r
+               }\r
+               if (p != 0)\r
+               {\r
+                    l = sl[p][0];\r
+                    r = sl[p][1];\r
+                    p--;\r
+               }\r
+          }\r
+          while(p > 0);\r
+     }\r
+     \r
+     main()\r
+     {\r
+          int n;\r
+     \r
+          /* Initialise arbitrary data */\r
+          strcpy(data[0],"RED");\r
+          strcpy(data[1],"BLUE");\r
+          strcpy(data[2],"GREEN");\r
+          strcpy(data[3],"YELLOW");\r
+          strcpy(data[4],"INDIGO");\r
+          strcpy(data[5],"BROWN");\r
+          strcpy(data[6],"BLACK");\r
+          strcpy(data[7],"ORANGE");\r
+          strcpy(data[8],"PINK");\r
+          strcpy(data[9],"CYAN");\r
+     \r
+          /* Set last element indicator */\r
+          lastone = 9;\r
+     \r
+          /* Call quick sort function */\r
+          QKSORT();\r
+     \r
+          /* Display sorted list */\r
+          for(n = 0; n < 10; n++)\r
+               puts(data[n]);\r
+     \r
+     }\r
+     \r
+A table sorted into ascending order can be searched with bsearch(), this\r
+takes the format;\r
+\r
+     bsearch(key,base,num_elements,width,int (*cmp)(void *, void *));\r
+\r
+bsearch() returns a pointer to the first element in the table that\r
+matches the key, or zero if no match is found.\r
+\r
+Or you can write your own binary search function thus;\r
+\r
+     int BSRCH(char *key, void *data, int numele, int width)\r
+     {\r
+          /* A binary search function returning one if found */\r
+          /* Zero if not found */\r
+     \r
+          int bp;\r
+          int tp;\r
+          int mp;\r
+          int result;\r
+          char *p;\r
+     \r
+          bp = 0;\r
+          tp = numele;\r
+          mp = (tp + bp) / 2;\r
+     \r
+          /* Locate element mp in table by assigning pointer to start */\r
+          /* and incrementing it by width * mp */\r
+          p = data;\r
+          p += width * mp;\r
+     \r
+          while((result = strcmp(p,key)) != 0)\r
+          {\r
+               if (mp >= tp)\r
+                    /* Not found! */\r
+                    return(0);\r
+               if (result < 0)\r
+                    bp = mp + 1;\r
+               else\r
+                    tp = mp - 1;\r
+     \r
+               mp = (bp + tp) / 2;\r
+               p = data;\r
+               p += width * mp;\r
+          }\r
+          return(1);\r
+     }\r
+     \r
+     void main()\r
+     {\r
+          int result;\r
+          char data[10][20];\r
+     \r
+          /* Initialise some arbirary data */\r
+     \r
+          strcpy(data[0],"RED");\r
+          strcpy(data[1],"BLUE");\r
+          strcpy(data[2],"GREEN");\r
+          strcpy(data[3],"YELLOW");\r
+          strcpy(data[4],"INDIGO");\r
+          strcpy(data[5],"BROWN");\r
+          strcpy(data[6],"BLACK");\r
+          strcpy(data[7],"ORANGE");\r
+          strcpy(data[8],"PINK");\r
+          strcpy(data[9],"CYAN");\r
+     \r
+          /* Sort the data table */\r
+          qsort(data[0],10,20,strcmp);\r
+     \r
+          result = BSRCH("CYAN",data[0],10,20);\r
+     \r
+          printf("\n%s\n",(result == 0) ? "Not found" : "Located okay");\r
+     }\r
+     \r
+There are other sorting algorithms as well. This program incorporates the\r
+QUICK SORT, BUBBLE SORT, FAST BUBBLE SORT, INSERTION SORT and SHELL SORT\r
+for comparing how each performs on a random 1000 item string list;\r
+\r
+     #include <stdio.h>\r
+     #include <stdlib.h>\r
+     #include <string.h>\r
+     \r
+     char data[1000][4];\r
+     char save[1000][4];\r
+     \r
+     int lastone;\r
+     \r
+     void INITDATA(void);\r
+     void QKSORT(void);\r
+     void SHELL(void);\r
+     void BUBBLE(void);\r
+     void FBUBBLE(void);\r
+     void INSERTION(void);\r
+     void MKDATA(void);\r
+     \r
+     void QKSORT()\r
+     {\r
+          /* Implementation of QUICKSORT algorithm */\r
+     \r
+          int i;\r
+          int j;\r
+          int l;\r
+          int p;\r
+          int r;\r
+          int s;\r
+          char temp[20];\r
+          static int sl[1000][2];\r
+     \r
+          l = 0;\r
+          r = lastone;\r
+          p = 0;\r
+     \r
+          do\r
+          {\r
+               while(l < r)\r
+               {\r
+                    i = l;\r
+                    j = r;\r
+                    s = -1;\r
+     \r
+                    while(i < j)\r
+                    {\r
+                         if (strcmp(data[i],data[j]) > 0)\r
+                         {\r
+                              strcpy(temp,data[i]);\r
+                              strcpy(data[i],data[j]);\r
+                              strcpy(data[j],temp);\r
+                              s = 0 - s;\r
+                         }\r
+     \r
+                         if (s == 1)\r
+                              i++;\r
+                         else\r
+                              j--;\r
+                    }\r
+     \r
+                    if (i + 1 < r)\r
+                    {\r
+                         p++;\r
+                         sl[p][0] = i + 1;\r
+                         sl[p][1] = r;\r
+                    }\r
+                    r = i - 1;\r
+               }\r
+               if (p != 0)\r
+               {\r
+                    l = sl[p][0];\r
+                    r = sl[p][1];\r
+                    p--;\r
+               }\r
+          }\r
+          while(p > 0);\r
+     }\r
+     \r
+     void SHELL()\r
+     {\r
+          /* SHELL Sort Courtesy of K & R */\r
+     \r
+          int gap;\r
+          int i;\r
+          int j;\r
+          char temp[20];\r
+     \r
+          for(gap = lastone / 2; gap > 0; gap /= 2)\r
+          for(i = gap; i < lastone; i++)\r
+               for(j = i - gap; j >= 0 && strcmp(data[j] , data[j + gap])\r
+     > 0;\r
+                      j -= gap)\r
+               {\r
+                    strcpy(temp,data[j]);\r
+                    strcpy(data[j] , data[j + gap]);\r
+                    strcpy(data[j + gap] , temp);\r
+               }\r
+     }\r
+     \r
+     void BUBBLE()\r
+     {\r
+          int a;\r
+          int b;\r
+          char temp[20];\r
+     \r
+          for(a = lastone; a >= 0; a--)\r
+          {\r
+               for(b = 0; b < a; b++)\r
+               {\r
+                    if(strcmp(data[b],data[b + 1]) > 0)\r
+                    {\r
+                         strcpy(temp,data[b]);\r
+                         strcpy(data[b] , data[b + 1]);\r
+                         strcpy(data[b + 1] , temp);\r
+                    }\r
+               }\r
+          }\r
+     }\r
+     \r
+     void FBUBBLE()\r
+     {\r
+          /* bubble sort with swap flag*/\r
+     \r
+          int a;\r
+          int b;\r
+          int s;\r
+          char temp[20];\r
+     \r
+          s = 1;\r
+     \r
+          for(a = lastone; a >= 0 && s == 1; a--)\r
+          {\r
+               s = 0;\r
+               for(b = 0; b < a; b++)\r
+               {\r
+                    if(strcmp(data[b],data[b + 1]) > 0)\r
+                    {\r
+                         strcpy(temp,data[b]);\r
+                         strcpy(data[b] , data[b + 1]);\r
+                         strcpy(data[b + 1] , temp);\r
+                         s = 1;\r
+                    }\r
+               }\r
+          }\r
+     }\r
+     \r
+     void INSERTION()\r
+     {\r
+          int a;\r
+          int b;\r
+          char temp[20];\r
+     \r
+          for(a = 0; a < lastone; a++)\r
+          {\r
+               b = a;\r
+               strcpy(temp,data[a + 1]);\r
+               while(b >= 0)\r
+               {\r
+                    if (strcmp(temp,data[b]) < 0)\r
+                    {\r
+                         strcpy(data[b+1],data[b]);\r
+                         b--;\r
+                    }\r
+                    else\r
+                         break;\r
+               }\r
+               strcpy(data[b+1],temp);\r
+          }\r
+     }\r
+     \r
+     void MKDATA()\r
+     {\r
+          /* Initialise arbitrary data */\r
+          /* Uses random(), which is not ANSI C */\r
+          /* Returns a random number between 0 and n - 1 */\r
+     \r
+          int n;\r
+          for(n = 0; n < 1000; n++)\r
+               sprintf(save[n],"%d",random(1000));\r
+     }\r
+     \r
+     void INITDATA()\r
+     {\r
+          int n;\r
+     \r
+          for(n = 0 ; n < 1000; n++)\r
+               strcpy(data[n],save[n]);\r
+     }\r
+     \r
+     void main()\r
+     {\r
+          MKDATA();\r
+     \r
+          /* Initialise arbitrary data */\r
+          INITDATA();\r
+     \r
+          /* Set last element indicator */\r
+          lastone = 999;\r
+     \r
+          /* Call quick sort function */\r
+          QKSORT();\r
+     \r
+     \r
+          /* Initialise arbitrary data */\r
+          INITDATA();\r
+     \r
+          /* Set last element indicator */\r
+          lastone = 1000;\r
+     \r
+          /* Call shell sort function */\r
+          SHELL();\r
+     \r
+          /* Initialise arbitrary data */\r
+          INITDATA();\r
+     \r
+          /* Set last element indicator */\r
+          lastone = 999;\r
+     \r
+          /* Call bubble sort function */\r
+          BUBBLE();\r
+     \r
+          /* Initialise arbitrary data */\r
+          INITDATA();\r
+     \r
+          /* Set last element indicator */\r
+          lastone = 999;\r
+     \r
+          /* Call bubble sort with swap flag function */\r
+          FBUBBLE();\r
+     \r
+          /* Initialise arbitrary data */\r
+          INITDATA();\r
+     \r
+          /* Set last element indicator */\r
+          lastone = 999;\r
+     \r
+          /* Call insertion sort function */\r
+          INSERTION();\r
+     }\r
+\r
+Here are the profiler results of the above test program run on 1000 and\r
+5000 random items;\r
+\r
+STRING SORT - 1000 RANDOM ITEMS\r
+\r
+FBUBBLE        26.436 sec  41%\r
+|********************************************\r
+BUBBLE         26.315 sec  41%\r
+|*******************************************\r
+INSERTION      10.210 sec  15% |***************\r
+SHELL               0.8050 sec   1%  |*\r
+QKSORT         0.3252 sec  <1% |\r
+\r
+STRING SORT - 5000 RANDOM ITEMS\r
+\r
+FBUBBLE        563.70 sec  41%\r
+|********************************************\r
+BUBBLE         558.01 sec  41%\r
+|********************************************\r
+INSERTION      220.61 sec  16% |***************\r
+SHELL               5.2531 sec  <1% |\r
+QKSORT         0.8379 sec  <1% |\r
+\r
+Here is the same test program amended for sorting tables of integers;\r
+\r
+     /* Integer sort test program */\r
+     \r
+     #include <stdio.h>\r
+     #include <stdlib.h>\r
+     \r
+     void INITDATA(void);\r
+     void QKSORT(void);\r
+     void SHELL(void);\r
+     void BUBBLE(void);\r
+     void FBUBBLE(void);\r
+     void INSERTION(void);\r
+     void MKDATA(void);\r
+     \r
+     int data[1000];\r
+     int save[1000];\r
+     \r
+     int lastone;\r
+     \r
+     void QKSORT()\r
+     {\r
+          /* Implementation of QUICKSORT algorithm */\r
+     \r
+          int i;\r
+          int j;\r
+          int l;\r
+          int p;\r
+          int r;\r
+          int s;\r
+          int temp;\r
+          static int sl[1000][2];\r
+     \r
+          l = 0;\r
+          r = lastone;\r
+          p = 0;\r
+     \r
+          do\r
+          {\r
+               while(l < r)\r
+               {\r
+                    i = l;\r
+                    j = r;\r
+                    s = -1;\r
+     \r
+                    while(i < j)\r
+                    {\r
+                         if (data[i] > data[j])\r
+                         {\r
+                              temp = data[i];\r
+                              data[i] = data[j];\r
+                              data[j] = temp;\r
+                              s = 0 - s;\r
+                         }\r
+     \r
+                         if (s == 1)\r
+                              i++;\r
+                         else\r
+                              j--;\r
+                    }\r
+     \r
+                    if (i + 1 < r)\r
+                    {\r
+                         p++;\r
+                         sl[p][0] = i + 1;\r
+                         sl[p][1] = r;\r
+                    }\r
+                    r = i - 1;\r
+               }\r
+               if (p != 0)\r
+               {\r
+                    l = sl[p][0];\r
+                    r = sl[p][1];\r
+                    p--;\r
+               }\r
+          }\r
+          while(p > 0);\r
+     }\r
+     \r
+     void SHELL()\r
+     {\r
+          /* SHELL Sort Courtesy of K & R */\r
+     \r
+          int gap;\r
+          int i;\r
+          int j;\r
+          int temp;\r
+     \r
+          for(gap = lastone / 2; gap > 0; gap /= 2)\r
+          for(i = gap; i < lastone; i++)\r
+               for(j = i - gap; j >= 0 && data[j] > data[j + gap];\r
+                      j -= gap)\r
+               {\r
+                    temp = data[j];\r
+                    data[j] = data[j + gap];\r
+                    data[j + gap] = temp;\r
+               }\r
+     }\r
+     \r
+     void BUBBLE()\r
+     {\r
+          int a;\r
+          int b;\r
+          int temp;\r
+     \r
+          for(a = lastone; a >= 0; a--)\r
+          {\r
+               for(b = 0; b < a; b++)\r
+               {\r
+                    if(data[b] > data[b + 1])\r
+                    {\r
+                         temp = data[b];\r
+                         data[b] = data[b + 1];\r
+                         data[b + 1] = temp;\r
+                    }\r
+               }\r
+          }\r
+     }\r
+     \r
+     void FBUBBLE()\r
+     {\r
+          /* bubble sort with swap flag */\r
+     \r
+          int a;\r
+          int b;\r
+          int s;\r
+          int temp;\r
+     \r
+          s = 1;\r
+     \r
+          for(a = lastone; a >= 0 && s == 1; a--)\r
+          {\r
+               s = 0;\r
+               for(b = 0; b < lastone - a; b++)\r
+               {\r
+                    if(data[b] > data[b + 1])\r
+                    {\r
+                         temp = data[b];\r
+                         data[b] = data[b + 1];\r
+                         data[b + 1] = temp;\r
+                         s = 1;\r
+                    }\r
+               }\r
+          }\r
+     }\r
+     \r
+     void INSERTION()\r
+     {\r
+          int a;\r
+          int b;\r
+          int temp;\r
+     \r
+          for(a = 0; a < lastone; a++)\r
+          {\r
+               b = a;\r
+               temp = data[a + 1];\r
+               while(b >= 0)\r
+               {\r
+                    if (temp < data[b])\r
+                    {\r
+                         data[b+1] = data[b];\r
+                         b--;\r
+                    }\r
+                    else\r
+                         break;\r
+               }\r
+               data[b+1] = temp;\r
+          }\r
+     }\r
+     \r
+     void MKDATA()\r
+     {\r
+          int n;\r
+     \r
+          for(n = 0; n < 1000; n++)\r
+               save[n] = random(1000);\r
+     }\r
+     \r
+     void INITDATA()\r
+     {\r
+          int n;\r
+     \r
+          for(n = 0; n < 1000; n++)\r
+          data[n] = save[n];\r
+     }\r
+     \r
+     void main()\r
+     {\r
+          int n;\r
+     \r
+          /* Create 1000 random elements */\r
+          MKDATA();\r
+     \r
+          /* Initialise arbitrary data */\r
+          INITDATA();\r
+     \r
+          /* Set last element indicator */\r
+          lastone = 999;\r
+     \r
+          /* Call quick sort function */\r
+          QKSORT();\r
+     \r
+          /* Initialise arbitrary data */\r
+          INITDATA();\r
+     \r
+          /* Set last element indicator */\r
+          lastone = 1000;\r
+     \r
+          /* Call shell sort function */\r
+          SHELL();\r
+     \r
+          /* Initialise arbitrary data */\r
+          INITDATA();\r
+     \r
+          /* Set last element indicator */\r
+          lastone = 999;\r
+     \r
+          /* Call bubble sort function */\r
+          BUBBLE();\r
+     \r
+          /* Initialise arbitrary data */\r
+          INITDATA();\r
+     \r
+          /* Set last element indicator */\r
+          lastone = 999;\r
+     \r
+          /* Call bubble sort with swap flag function */\r
+          FBUBBLE();\r
+     \r
+          /* Initialise arbitrary data */\r
+          INITDATA();\r
+     \r
+          /* Set last element indicator */\r
+          lastone = 999;\r
+     \r
+          /* Call insertion sort function */\r
+          INSERTION();\r
+     }\r
+\r
+And here are the profiler results for this program;\r
+\r
+INTEGER SORTS - 1000 RANDOM NUMBERS (0 - 999)\r
+\r
+FBUBBLE        3.7197 sec  41%\r
+|********************************************\r
+BUBBLE         3.5981 sec  39%\r
+|******************************************\r
+INSERTION      1.4258 sec  15% |***************\r
+SHELL               0.1207 sec   1%  |*\r
+QKSORT         0.0081 sec  <1% |\r
+\r
+INTEGER SORTS - 5000 RANDOM NUMBERS (0 - 999)\r
+\r
+FBUBBLE        92.749 sec  42%\r
+|********************************************\r
+BUBBLE         89.731 sec  41%\r
+|********************************************\r
+INSERTION      35.201 sec  16% |***************\r
+SHELL               0.9838 sec  <1% |\r
+QKSORT         0.0420 sec  <1% |\r
+\r
+INTEGER SORTS - 5000 RANDOM NUMBERS (0 - 99)\r
+\r
+FBUBBLE        92.594 sec  42% |*****************************************\r
+BUBBLE         89.595 sec  40% |****************************************\r
+INSERTION      35.026 sec  16% |***************\r
+SHELL               0.7563 sec  <1% |\r
+QKSORT         0.6018 sec  <1% |\r
+\r
+INTEGER SORTS - 5000 RANDOM NUMBERS (0 - 9)\r
+\r
+FBUBBLE        89.003 sec  41%\r
+|*******************************************\r
+BUBBLE         86.921 sec  40%\r
+|*******************************************\r
+INSERTION      31.544 sec  14% |***************\r
+QKSORT         6.0358 sec   2%  |**\r
+SHELL               0.5424 sec  <1% |\r
+\r
+INTEGER SORTS - 5000 DESCENDING ORDERED NUMBERS\r
+\r
+FBUBBLE        122.99 sec  39%\r
+|******************************************\r
+BUBBLE         117.22 sec  37% |****************************************\r
+INSERTION      70.595 sec  22% |**********************\r
+SHELL               0.6438 sec  <1% |\r
+QKSORT         0.0741 sec  <1% |\r
+\r
+INTEGER SORTS - 5000 ORDERED NUMBERS\r
+\r
+BUBBLE         62.908 sec  99%\r
+|******************************************\r
+SHELL               0.3971 sec  <1% |\r
+INSERTION      0.0510 sec  <1% |\r
+QKSORT         0.0382 sec  <1% |\r
+FBUBBLE        0.0251 sec  <1% |\r
+\r
+INTEGER SORTS - 10000 RANDOM NUMBERS (0 - 999)\r
+\r
+FBUBBLE        371.18 sec  42% |****************************************\r
+BUBBLE         359.06 sec  41% |***************************************\r
+INSERTION      140.88 sec  16% |**************\r
+SHELL               2.0423 sec  <1% |\r
+QKSORT         0.6183 sec  <1% |\r
+\r
+Theory has it that the performance of a sorting algorithm is dependant\r
+upon;\r
+\r
+     a) the number of items to be sorted and\r
+     b) how unsorted the list is to start with.\r
+\r
+With this in mind it is worth testing the various sorting routines\r
+described here to determine which one will best suit your particular\r
+application. If you examine the above profiler results you will see that:\r
+\r
+     1) With an already sorted list FBUBBLE() executes fastest\r
+     \r
+     2) With a random list of small variations between the values SHELL()\r
+         executes fastest\r
+     \r
+     3) With a random list of large variations between the values\r
+     QKSORT()\r
+          executes the fastest\r
+\r
+What the profiler does not highlight is that when the comparison aspect\r
+of a sort function takes a disproportionately long time to execute in\r
+relation to the rest of the sort function, then the bubble sort with a\r
+swap flag will execute faster than the bubble sort with out a swap flag.\r
+\r
+When considering a sort routine take into consideration memory\r
+constraints and the type of data to be sorted as well as the relative\r
+performances of the sort functions. Generally, the faster a sort\r
+operates, the more memory it requires. Compare the simple bubble sort\r
+with the quick sort, and you will see that the quick sort requires far\r
+more memory than the bubble sort.\r
+\r
+                                    \r
+                        DYNAMIC MEMORY ALLOCATION\r
+\r
+\r
+If a program needs a table of data, but the size of the table is\r
+variable, perhaps for a list of all file names in the current directory,\r
+it is inefficient to waste memory by declaring a data table of the\r
+maximum possible size. Rather it is better to dynamically allocate the\r
+table as required.\r
+\r
+Turbo C allocates RAM as being available for dynamic allocation into an\r
+area called the "heap". The size of the heap varies with memory model.\r
+The tiny memory model defaults to occupy 64K of RAM. The small memory\r
+model allocates upto 64K for the program/code and heap with a far heap\r
+being available within the remainder of conventional memory. The other\r
+memory models make all conventional memory available to the heap. This is\r
+significant when programming in the tiny memory model when you want to\r
+reduce the memory overhead of your program to a minimum. The way to do\r
+this is to reduce the heap to a minimum size. The smallest is 1 byte.\r
+\r
+C provides a function malloc() which allocates a block of free memory of\r
+a specified size and returns a pointer to the start of the block; it also\r
+provides free() which deallocates a block of memory previously allocated\r
+by malloc(). Notice, however, that the IBM PC doesnot properly free\r
+blocks of memory, and contiuous use of malloc() and free() will\r
+fragmentise memory, eventually causing no memory to be available untill\r
+the program terminates.\r
+\r
+This program searches a specified file for a specified string (with case\r
+sensitivity). It uses malloc() to allocate just enough memory for the\r
+file to be read into memory.\r
+\r
+     #include <stdio.h>\r
+     #include <stdlib.h>\r
+     \r
+     char *buffer;\r
+     \r
+     void main(int argc, char *argv[])\r
+     {\r
+          FILE *fp;\r
+          long flen;\r
+     \r
+          /* Check number of parameters */\r
+          if (argc != 3)\r
+          {\r
+               fputs("Usage is sgrep <text> <file spec>",stderr);\r
+               exit(0);\r
+          }\r
+     \r
+          /* Open stream fp to file */\r
+          fp = fopen(argv[2],"r");\r
+          if (!fp)\r
+          {\r
+               perror("Unable to open source file");\r
+               exit(0);\r
+          }\r
+     \r
+          /* Locate file end */\r
+          if(fseek(fp,0L,SEEK_END))\r
+          {\r
+               fputs("Unable to determine file length",stderr);\r
+               fclose(fp);\r
+               exit(0);\r
+          }\r
+     \r
+          /* Determine file length */\r
+          flen = ftell(fp);\r
+     \r
+          /* Check for error */\r
+          if (flen == -1L)\r
+          {\r
+               fputs("Unable to determine file length",stderr);\r
+               fclose(fp);\r
+               exit(0);\r
+          }\r
+     \r
+          /* Set file pointer to start of file */\r
+          rewind(fp);\r
+     \r
+          /* Allocate memory buffer */\r
+          buffer = malloc(flen);\r
+     \r
+          if (!buffer)\r
+          {\r
+               fputs("Unable to allocate memory",stderr);\r
+               fclose(fp);\r
+               exit(0);\r
+          }\r
+     \r
+          /* Read file into buffer */\r
+          fread(buffer,flen,1,fp);\r
+     \r
+          /* Check for read error */\r
+          if(ferror(fp))\r
+          {\r
+               fputs("Unable to read file",stderr);\r
+     \r
+               /* Deallocate memory block */\r
+               free(buffer);\r
+     \r
+               fclose(fp);\r
+               exit(0);\r
+          }\r
+     \r
+          printf("%s %s in %s",argv[1],(strstr(buffer,argv[1])) ? "was\r
+     found" : "was not found",argv[2]);\r
+     \r
+          /* Deallocate memory block before exiting */\r
+          free(buffer);\r
+          fclose(fp);\r
+     }\r
+                                    \r
+                         VARIABLE ARGUMENT LISTS\r
+\r
+\r
+Some functions, such as printf(), accept a variable number and type of\r
+arguments. C provides a mechanism to write your own functions which can\r
+accept a variable argument list. This mechanism is the va_ family defined\r
+in the header file `stdarg.h'.\r
+\r
+There are three macros which allow implementation of variable argument\r
+lists; va_start(), va_arg() and va_end() and a variable type va_list\r
+which defines an array which holds the information required by the\r
+macros.\r
+\r
+va_start() takes two parameters, the first is the va_list variable and\r
+the second is the last fixed parameter sent to the function. va_start()\r
+must be called before attempting to access the variable argument list as\r
+it sets up pointers required by the other macros.\r
+\r
+va_arg() returns the next variable from the argument list. It is called\r
+with two parameters, the first is the va_list variable and the second is\r
+the type of the argument to be extracted. So, if the next variable in the\r
+argument list is an integer, it can be extracted with;\r
+\r
+\r
+     <int> = va_arg(<va_list>,int);\r
+\r
+va_end() is called after extracting all required variables from the\r
+argument list, and simply tidies up the internal stack if appropriate.\r
+va_end() accepts a single parameter, the va_list variable.\r
+\r
+The following simple example program illustrates the basis for a printf()\r
+type implementation where the types of the arguments is not known, but\r
+can be determined from the fixed parameter. This example only caters for\r
+integer, string and character types, but could easily by extended to\r
+cater for other variable types as well by following the method\r
+illustrated;\r
+\r
+     #include <stdarg.h>\r
+     \r
+     char *ITOS(long x, char *ptr)\r
+     {\r
+          /* Convert a signed decimal integer to a string */\r
+     \r
+          long pt[9] = { 100000000, 10000000, 1000000, 100000, 10000,\r
+     1000, 100, 10, 1 };\r
+          int n;\r
+     \r
+          /* Check sign */\r
+          if (x < 0)\r
+          {\r
+               *ptr++ = '-';\r
+               /* Convert x to absolute */\r
+               x = 0 - x;\r
+          }\r
+     \r
+          for(n = 0; n < 9; n++)\r
+          {\r
+               if (x > pt[n])\r
+               {\r
+                    *ptr++ = 48 + x / pt[n];\r
+                    x %= pt[n];\r
+               }\r
+          }\r
+          return(ptr);\r
+     }\r
+     \r
+     void varfunc(char *format, ...)\r
+     {\r
+          va_list arg_ptr;\r
+          char output[1000];\r
+          char *ptr;\r
+          int bytes;\r
+          int x;\r
+          char *y;\r
+          char z;\r
+     \r
+          /* Initialise pointer to argument list */\r
+          va_start(arg_ptr, format);\r
+     \r
+          /* loop format string */\r
+          ptr = output;\r
+          bytes = 0;\r
+          while(*format)\r
+          {\r
+               /* locate next argument */\r
+               while(*format != '%')\r
+               {\r
+                    *ptr++ = *format++;\r
+                    bytes++;\r
+               }\r
+               /* A % has been located */\r
+               format++;\r
+               switch(*format)\r
+               {\r
+                    case '%' :  *ptr++ = '%';\r
+                             break;\r
+     \r
+                    case 'd' : /* integer expression follows */\r
+                            x = va_arg(arg_ptr,int);\r
+                            ptr = ITOS(x,ptr);\r
+                            *ptr = 0;\r
+                            format++;\r
+                            bytes += strlen(output) - bytes;\r
+                            break;\r
+     \r
+                    case 's' : /* String expression follows */\r
+                            y = va_arg(arg_ptr,char *);\r
+                            strcat(output,y);\r
+                            x = strlen(y);\r
+                            format++;\r
+                            ptr += x;\r
+                            bytes += x;\r
+                            break;\r
+     \r
+                    case 'c' : /* Char expression follows */\r
+                            z = va_arg(arg_ptr,char);\r
+                            *ptr++ = z;\r
+                            format++;\r
+                            bytes++;\r
+                            break;\r
+               }\r
+     \r
+          }\r
+     \r
+          /* Clean stack just in case! */\r
+          va_end(arg_ptr);\r
+     \r
+          /* Null terminate output string */\r
+          *ptr = 0;\r
+     \r
+          /* Display what we got */\r
+          printf("\nOUTPUT==%s",output);\r
+     }\r
+     \r
+     void main()\r
+     {\r
+          varfunc("%d %s %c",5,"hello world",49);\r
+     }\r
+     \r
+\r
+A simpler variation is to use vsprintf() rather than implementing our own\r
+variable argument list access. However, it is beneficial to understand\r
+how variable argument lists behave. The following is a simplification of\r
+the same program, but leaving the dirty work to the compiler;\r
+\r
+     #include <stdio.h>\r
+     #include <stdarg.h>\r
+     \r
+     void varfunc(char *format, ...)\r
+     {\r
+          va_list arg_ptr;\r
+          char output[1000];\r
+     \r
+          va_start(arg_ptr, format);\r
+     \r
+          vsprintf(output,format,arg_ptr);\r
+     \r
+          va_end(arg_ptr);\r
+     \r
+          /* Display what we got */\r
+          printf("\nOUTPUT==%s",output);\r
+     }\r
+     \r
+     void main()\r
+     {\r
+          varfunc("%d %s %c",5,"hello world",49);\r
+     }\r
+                                    \r
+                         TRIGONOMETRY FUNCTIONS\r
+\r
+The ANSI standard on C defines a number of trigonometry functions, all of\r
+which accept an angle parameter in radians;\r
+\r
+\r
+FUNCTION  PROTOTYPE                DESCRIPTION\r
+acos      double acos(double x)              arc cosine of x\r
+asin      double asin(double x)              arc sine of x\r
+atan      double atan(double x)              arc tangent of x\r
+atan2          double atan2(double x, double y)        arc tangent of y/x\r
+cos       double cos(double x)               cosine of x\r
+cosh      double cosh(double x)              hyperbolic cosine of x\r
+sin       double sin(double x)               sine of x\r
+sinh      double sinh(double x)              hyperbolic sine of x\r
+tan       double tan(double x)               tangent of x\r
+tanh      double tanh(double x)              hyperbolic tangent of x\r
+\r
+There are 2PI radians in a circle, therefore 1 radian is equal to 360/2PI\r
+degrees or approximately 57 degrees in 1 radian. The calculation of any\r
+of the above functions requires large floating point numbers to be used\r
+which is a very slow process. If you are going to use calls to a trig'\r
+function, it is a good idea to use a lookup table of values rather than\r
+keep on calling the function. This approach is used in the discussion on\r
+circle drawing later in this book.\r
+\r
+                                    \r
+                                 ATEXIT\r
+\r
+\r
+When ever a program terminates, it should close any open files (this is\r
+done for you by the C compiler's startup/termination code which it\r
+surrounds your program with), and restore the host computer to some\r
+semblance of order.  Within a large program where exit may occur from a\r
+number of locations it is a pain to have to keep on writing calls to the\r
+cleanup routine. Fortunately we don't have to!\r
+\r
+The ANSI standard on C describes a function, atexit(), which registers\r
+the specified function, supplied as a parameter to atexit(), as a\r
+function which is called immediately before terminating the program. This\r
+function is called automatically, so the following program calls\r
+`leave()' whether an error occurs or not;\r
+\r
+     #include <stdio.h>\r
+     \r
+     void leave()\r
+     {\r
+          puts("\nBye Bye!");\r
+     }\r
+     \r
+     void main()\r
+     {\r
+          FILE *fp;\r
+          int a;\r
+          int b;\r
+          int c;\r
+          int d;\r
+          int e;\r
+          char text[100];\r
+     \r
+          atexit(leave);\r
+     \r
+          fp = fopen("data.txt","w");\r
+     \r
+          if(!fp)\r
+          {\r
+               perror("Unable to create file");\r
+               exit(0);\r
+          }\r
+     \r
+          fprintf(fp,"1 2 3 4 5 \"A line of numbers\"");\r
+     \r
+          fflush(fp);\r
+     \r
+          if (ferror(fp))\r
+          {\r
+               fputs("Error flushing stream",stderr);\r
+               exit(1);\r
+          }\r
+     \r
+          rewind(fp);\r
+          if (ferror(fp))\r
+          {\r
+               fputs("Error rewind stream",stderr);\r
+               exit(1);\r
+          }\r
+     \r
+          fscanf(fp,"%d %d %d %d %d %s",&a,&b,&c,&d,&e,text);\r
+          if (ferror(fp))\r
+          {\r
+               /* Unless you noticed the deliberate bug earlier */\r
+               /* The program terminates here */\r
+               fputs("Error reading from stream",stderr);\r
+               exit(1);\r
+          }\r
+     \r
+          printf("\nfscanf() returned %d %d %d %d %d %s",a,b,c,d,e,text);\r
+     }\r
+                                    \r
+                            INCREASING SPEED\r
+\r
+\r
+In order to reduce the time your program spends executing it is essential\r
+to know your host computer. Most computers are very slow at displaying\r
+information on the screen. And the IBM PC is no exception to this. C\r
+offers various functions for displaying data, printf() being one of the\r
+most commonly used and also the slowest. Whenever possible try to use\r
+puts(varname) in place of printf("%s\n",varname). Remembering that puts()\r
+appends a newline to the string sent to the screen.\r
+\r
+When multiplying a variable by a constant which is a factor of 2 many C\r
+compilers will recognise that a left shift is all that is required in the\r
+assembler code to carry out the multiplication rapidly. When multiplying\r
+by other values it is often faster to do a multiple addition instead, so;\r
+\r
+\r
+     'x * 3' becomes 'x + x + x'\r
+\r
+Don't try this with variable multipliers in a loop because it becomes\r
+very slow! But, where the multiplier is a constant it can be faster.\r
+(Sometimes!) Another way to speed up multiplication and division is with\r
+the shift commands, << and >>.\r
+\r
+The instruction x /= 2 can equally well be written x >>= 1, shift the\r
+bits of x right one place. Many compilers actually convert integer\r
+divisions by 2 into a shift right instruction. You can use the shifts for\r
+multiplying and dividing by 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024 &c.\r
+If you have difficulty understanding the shift commands consider the\r
+binary form of a number;\r
+\r
+\r
+     01001101  equal to   77\r
+\r
+shifted right one place it becomes;\r
+\r
+     00100110  equal to   38\r
+\r
+Try to use integers rather than floating point numbers where ever\r
+possible.  Sometimes you can use integers where you didn't think you\r
+could! For example, to convert a fraction to a decimal one would normally\r
+say;\r
+\r
+     percentage = x / y * 100\r
+\r
+This requires floating point variables. However, it can also be written\r
+as;\r
+\r
+     z = x * 100;\r
+     percentage = z / y\r
+\r
+Which works fine with integers, so long as you don't mind the percentage\r
+being truncated. eg;\r
+\r
+     5 / 7 * 100 is equal to 71.43 with floating point\r
+\r
+but with integers;\r
+\r
+     5 * 100 / 7 is equal to 71\r
+\r
+(Assuming left to right expression evaluation. You may need to force the\r
+multiplication to be done first as with `z = x * 100').\r
+\r
+Here is a test program using this idea;\r
+\r
+     float funca(double x, double y)\r
+     {\r
+          return(x / y * 100);\r
+     }\r
+     \r
+     int funcb(int x,int y)\r
+     {\r
+          return(x * 100 / y);\r
+     }\r
+     \r
+     void main()\r
+     {\r
+          int n;\r
+          double x;\r
+          int y;\r
+     \r
+          for(n = 0; n < 5000; n++)\r
+          {\r
+               x = funca(5,7);\r
+               y = funcb(5,7);\r
+          }\r
+     }\r
+     \r
+And here is the results of the test program fed through a profiler;\r
+\r
+funca            1.9169 sec  96%\r
+|**********************************************\r
+funcb            0.0753 sec   3%  |*\r
+\r
+You can clearly see that the floating point function is 25 times slower\r
+than the integer equivalent!\r
+\r
+NB: Although it is normal practice for expressions to be evaluated left\r
+to right, the ANSI standard on C does not specify an order of preference\r
+for expression evaluation, and as such you should check your compiler\r
+manual.\r
+\r
+Another way of increasing speed is to use pointers rather than array\r
+indexing. When you access an array through an index, for example with;\r
+\r
+     x = data[i];\r
+\r
+the compiler has to calculate the offset of data[i] from the beginning of\r
+the array. A slow process. Using pointers can often improve things as the\r
+following two bubble sorts, one with array indexing and one with pointers\r
+illustrates;\r
+\r
+     void BUBBLE()\r
+     {\r
+          /* Bubble sort using array indexing */\r
+     \r
+          int a;\r
+          int b;\r
+          int temp;\r
+     \r
+          for(a = lastone; a >= 0; a--)\r
+          {\r
+               for(b = 0; b < a; b++)\r
+               {\r
+                    if(data[b] > data[b + 1])\r
+                    {\r
+                         temp = data[b];\r
+                         data[b] = data[b + 1];\r
+                         data[b + 1] = temp;\r
+                    }\r
+               }\r
+          }\r
+     }\r
+     \r
+     void PTRBUBBLE()\r
+     {\r
+          /* Bubble sort using pointers */\r
+     \r
+          int temp;\r
+          int *ptr;\r
+          int *ptr2;\r
+     \r
+          for(ptr = &data[lastone]; ptr >= data; ptr--)\r
+          {\r
+               for(ptr2 = data; ptr2 < ptr; ptr2++)\r
+               {\r
+                    if(*ptr2 > *(ptr2 + 1))\r
+                    {\r
+                         temp = *ptr2;\r
+                         *ptr2 = *(ptr2 + 1);\r
+                         *(ptr2 + 1) = temp;\r
+                    }\r
+               }\r
+          }\r
+     }\r
+     \r
+Here are the profiler results for the two versions of the same bubble\r
+sort operating on the same 1000 item, randomly sorted list;\r
+\r
+BUBBLE       3.1307 sec  59% |******************************************\r
+PTRBUBBLE    2.1686 sec  40% |***************************\r
+\r
+\r
+Here is another example of how to initialise an array using first the\r
+common indexing approach, and secondly the pointer approach;\r
+\r
+     /* Index array initialisation */\r
+     int n;\r
+     \r
+     for(n = 0; n < 1000; n++)\r
+          data[n] = random(1000);\r
+     \r
+     \r
+     /* Pointer array initialisation */\r
+     int *n;\r
+     \r
+     for(n = data; n < &data[1000]; n++)\r
+          *n = random(1000);\r
+     \r
+\r
+Needless to say, the pointer approach is faster than the index. The\r
+pointer approach is only really of benefit when an array is going to be\r
+traversed, as in the above examples. In the case of say a binary search\r
+where a different and non-adjacent element is going to be tested each\r
+pass then the pointer approach is no better than using array indexing.\r
+\r
+The exception to this rule of using pointers rather than indexed access,\r
+comes with pointer to pointers. Say your program has declared a table of\r
+static data, such as:\r
+\r
+static char *colours[] = { "Black", "Blue", "Green", "Yellow", "Red",\r
+"White" };\r
+\r
+It is faster to access the table with colours[n] than it is with a\r
+pointer, since each element in the table colours[], is a pointer. If you\r
+need to scan a string table for a value you can use this very fast\r
+approach instead;\r
+\r
+First the table is changed into a single string, with some delimiter\r
+between the elements.\r
+\r
+     static char *colours = "Black/Blue/Green/Yellow/Red/White";\r
+\r
+Then to confirm that a value is held in the table you can use strstr();\r
+\r
+     result = strstr(colours,"Cyan");\r
+\r
+Using in-line assembler code can provide the greatest speed increase.\r
+Care must be taken however not to interfere with the compiled C code. It\r
+is usually safe to write a complete function with in-line assembler code,\r
+but mixing in-line assembler with C code can be hazardous. As a rule of\r
+thumb, get your program working without assembler code, and then if you\r
+want to use in-line assembler, convert small portions of the code at a\r
+time, testing the program at each stage. Video I/O is a very slow process\r
+with C, and usually benefits from in-line assembler, and we have used\r
+this principle quite widely in the example programs which follow later.\r
+\r
+                                    \r
+                               PC GRAPHICS\r
+\r
+When programming graphics you should bear in mind that they are a machine\r
+dependant subject. Code to produce graphics on an IBM PC will not port to\r
+an Amiga, or VAX or any other type of computer.\r
+\r
+\r
+\r
+Introduction To PC Graphics\r
+\r
+The IBM PC and compatible range of computers display information on a\r
+visual display unit (VDU). To enable the computer to send information to\r
+the VDU a component is included within the computer called a "display\r
+card". There are various display cards and VDUs which have been produced\r
+for the IBM PC range of computers; monochrome display adapter (MDA),\r
+colour graphics adapter (CGA), Hercules graphics card (HGC), Enhanced\r
+graphics adapter (EGA), video graphics array (VGA), super video graphics\r
+array (SVGA), memory controller gate array (MCGA), 8514/A and the Txas\r
+Instruments Graphics Architecture (TIGA). For simplicity, this section\r
+will concern itself only with the three more common types of display;\r
+\r
+     CGA, EGA and VGA\r
+\r
+Information about the VGA display is also relevant to the SVGA display\r
+which in simple terms can do the same and more. This section will not\r
+concern itself with monochrome displays since they are of limited use in\r
+graphics work.\r
+\r
+\r
+Display Modes\r
+\r
+When an IBM PC computer is first switched on is set to display 80 columns\r
+by 25 rows of writing. This measurement, 80 x 25 is called the\r
+"resolution". A display mode which is intended for displaying writing is\r
+called a "text" mode.  Where as a display mode which is intended for\r
+displaying pictures is called a "graphics" mode.\r
+\r
+If you look closely at the display you will see that each displayed\r
+character is comprised of dots. In reality the entire display is\r
+comprised of dots, called "pixels", which may be set to different\r
+colours. In text display modes these pixels are not very relevant,\r
+however, in graphics display modes each pixel may be selected and set by\r
+a program. The size of the pixels varies with the display resolution. In\r
+80 x 25 text mode the pixels are half the width they are in 40 x 25 text\r
+display mode.\r
+\r
+Depending upon the display card installed in the computer, there are a\r
+number of display modes which may be used;\r
+\r
+\r
+MODE   TYPE               RESOLUTION         COLOURS\r
+                                             \r
+ 0       Text             40 x 25            4 (CGA), 16 (EGA,\r
+                                             VGA) Shades of\r
+                                             grey\r
+ 1       Text             40 x 25            4 (CGA), 16 (EGA,\r
+                                             VGA)\r
+ 2       Text             80 x 25            4 (CGA), 16 (EGA,\r
+                                             VGA) Shades of\r
+                                             grey\r
+ 3       Text             80 x 25            4 (CGA), 16 (EGA,\r
+                                             VGA)\r
+ 4       Graphics         320 x 200          4\r
+ 5       Graphics         320 x 200          4 (grey on CGA\r
+                                             and EGA)\r
+ 6       Graphics         640 x 200          2\r
+ 7       Text             80 x 25            Mono (EGA, VGA)\r
+13       Graphics         320 x 200          16 (EGA, VGA)\r
+14       Graphics         640 x 200          16 (EGA, VGA)\r
+15       Graphics         640 x 350          Mono (EGA, VGA)\r
+16       Graphics         640 x 350          16 (EGA, VGA)\r
+17       Graphics         640 x 480          2 (VGA)\r
+18       Graphics         640 x 480          16 (VGA)\r
+19       Graphics         320 x 200          256 (VGA)\r
+\r
+The term resolution in graphics modes refers to the number of pixels\r
+across and down the VDU. The larger the number of pixels, the smaller\r
+each is and the sharper any displayed image appears. As you can see from\r
+the table, the VGA display can produce a higher resolution than the other\r
+display cards, resulting in sharper images being produced.\r
+\r
+\r
+The CGA display card can produce a maximum resolution of 320 x 200\r
+pixels, where as the VGA display card can produce a resolution of 640 x\r
+480 pixels.  This is why writing on a VGA VDU looks so much sharper than\r
+the writing displayed on a CGA VDU.\r
+\r
+\r
+\r
+Accessing The Display\r
+\r
+Inside the IBM PC computer is a silicon chip called the BIOS ROM, this\r
+chip contains functions which may be called by an external computer\r
+program to access the display card, which in turn passes the information\r
+on to the VDU.  The BIOS display functions are all accessed by generating\r
+interrupt 10 calls, with the number of the appropriate function stored in\r
+the assembly language AH register.\r
+\r
+A programmer interested in creating graphics displays must first be able\r
+to switch the display card to an appropriate graphics mode. This is\r
+achieved by calling the BIOS display function 0, with th number of the\r
+desired display mode from the table stored in the assembly language AL\r
+register thus the following assembly language code segment will switch\r
+the display card to CGA graphics mode 4, assuming that is that the\r
+display card is capable of this mode;\r
+\r
+          mov   ah , 00\r
+          mov   al , 04\r
+          int   10h\r
+\r
+\r
+A C function for selecting video display modes can be written;\r
+\r
+     #include <dos.h>\r
+     \r
+     void setmode(unsigned char mode)\r
+     {\r
+          /* Sets the video display mode */\r
+     \r
+          union REGS inregs outreg;\r
+     \r
+          inreg.h.ah = 0;\r
+          inreg.h.al = mode;\r
+          int86(0x10,&inreg,&outregs);\r
+     }\r
+     \r
+Any graphics are created by setting different pixels to different\r
+colours, this is termed "plotting", and is achieved by calling BIOS\r
+display function 12 with the pixel's horizontal coordinate in the\r
+assembly language CX register and it's vertical coordinate in the\r
+assembly language DX register and the required colour in the assembly\r
+language AL register thus;\r
+\r
+\r
+          mov   ah, 12\r
+          mov   al, colour\r
+          mov   bh, 0\r
+          mov   cx, x_coord\r
+          mov   dx, y_coord\r
+          int   10h\r
+\r
+The corresponding C function is;\r
+\r
+     #include <dos.h>\r
+     \r
+     void plot(int x_coord, int y_coord, unsigned char colour)\r
+     {\r
+          /* Sets the colour of a pixel */\r
+     \r
+          union REGS regs;\r
+     \r
+          regs.h.ah = 12;\r
+          regs.h.al = colour;\r
+          regs.h.bh = 0;\r
+          regs.x.cx = x_coord;\r
+          regs.x.dx = y_coord;\r
+          int86(0x10,&regs,&regs);\r
+     }\r
+\r
+The inverse function of plot is to read the existing colour setting of a\r
+pixel. This is done by calling BIOS ROM display function 13, again with\r
+the pixel's horizontal coordinate in the assembly language CX register\r
+and it's vertical coordinate in the assembly language DX register. This\r
+function then returns the pixel's colour in the assembly language AL\r
+register;\r
+\r
+\r
+     #include <dos.h>\r
+     \r
+     unsigned char get_pixel(int x_coord, int y_coord)\r
+     {\r
+          /* Reads the colour of a pixel */\r
+     \r
+          union REGS inreg, outreg;\r
+     \r
+          inreg.h.ah = 13;\r
+          inreg.h.bh = 0;\r
+          inreg.x.cx = x_coord;\r
+          inreg.x.dx = y_coord;\r
+          int86(0x10,&inreg,&outreg);\r
+          return(outreg.h.al);\r
+     }\r
+\r
+\r
+Colour And The CGA\r
+\r
+The CGA display card can display a maximum of 4 colours simultaneously at\r
+any time. However, the display card can generate a total of 8 colours.\r
+There are two sets of colours, called "palettes". The first palette\r
+contains the colours;\r
+\r
+     background, cyan, magenta and white.\r
+\r
+the second palette contains the colours;\r
+\r
+       background, green, red and yellow.\r
+\r
+Colour 0 is always the same as the background colour.\r
+\r
+The pixels displayed on the VDU take their colours from the currently\r
+active palette, and are continuously being refreshed. So, if the active\r
+palette changes, so to do the colours of the displayed pixels on the VDU.\r
+\r
+Selection of the active CGA palette is achieved by calling the BIOS\r
+display function 11 with the number of the desired palette (either 0 or\r
+1) in the assembly language BH register;\r
+\r
+\r
+          mov   ah, 11\r
+          mov   bh, palette\r
+          int   10h\r
+\r
+The C function for selecting the CGA palette is;\r
+\r
+     void palette(unsigned char palette)\r
+     {\r
+          union REGS inreg, outreg;\r
+     \r
+          inreg.h.ah = 11;\r
+          inreg.h.bh = palette;\r
+          int86(0x10,&inreg,&outreg);\r
+     }\r
+     \r
+The background colour may be selected independantly from any of the eight\r
+available colours by calling the same BIOS display function with a value\r
+of 0 stored in the assembly language BH register and the desired\r
+background colour in the assembly language BL register;\r
+\r
+\r
+     mov   ah, 11\r
+     mov   bh, 0\r
+     mov   bl, colour\r
+     int   10h\r
+\r
+In C this function can be written;\r
+\r
+     void background(unsigned char colour)\r
+     {\r
+          union REGS inreg, outreg;\r
+     \r
+          inreg.h.ah = 11;\r
+          inreg.h.bh = 0;\r
+          inreg.h.bl = colour;\r
+          int86(0x10,&inreg,&outreg);\r
+     }\r
+     \r
+The background colours available are;\r
+\r
+     0       Black\r
+     1       Blue\r
+     2       Green\r
+     3       Cyan\r
+     4       Red\r
+     5       Magenta\r
+     6       Yellow\r
+     7       White\r
+\r
+\r
+Colour And The EGA\r
+\r
+The EGA display card can display a maximum of 16 colours simultaneously\r
+at any time. The colour of all pixels is continuously refreshed by the\r
+display card by reading the colour from the EGA palette. Unlike the CGA\r
+display card, the EGA display card allows you to redefine any or all of\r
+the colours in the palette. Unfortunately only the first 8 colours may be\r
+loaded into other palette colours.\r
+\r
+The colours are;\r
+\r
+     0    Black\r
+     1    Blue\r
+     2    Green\r
+     3    Cyan\r
+     4    Red\r
+     5    Magenta\r
+     6    Brown\r
+     7    Light grey\r
+     8    Dark grey\r
+     9    Light blue\r
+     10        Light green\r
+     11   Light cyan\r
+     12        Light red\r
+     13        Light magenta\r
+     14        Yellow\r
+     15        White\r
+\r
+Changing a palette colour is achieved by calling the BIOS display\r
+function 16 with a value of 0 in the assembly language AL register, the\r
+colour value (0 to 7) in the assembly language BH register and the number\r
+of the palette colour (0 to 15) in the assembly language BL register\r
+thus;\r
+\r
+\r
+     mov   ah,16\r
+     mov   al,0\r
+     mov   bl,palette\r
+     mov   bh,colour\r
+     int   10h\r
+\r
+In C this function may be written;\r
+\r
+     void ega_palette(unsigned char colour, unsigned char palette)\r
+     {\r
+          union REGS inreg,outreg;\r
+     \r
+          inreg.h.ah = 16;\r
+          inreg.h.al = 0;\r
+          inreg.h.bl = palette;\r
+          inreg.h.bh = colour;\r
+     \r
+          int86(0x10,&inreg,&outreg);\r
+     }\r
+\r
+\r
+\r
+Colour And The VGA\r
+\r
+The VGA display card can display a maximum of 256 colours on the VDU at\r
+any one time, these colours are defined by information held in 256\r
+special registers called "DAC" registers. As with the CGA and EGA\r
+displays, the colour of displayed pixels is continuously being refreshed,\r
+and as such any change to a DAC register is immediately visible. Each DAC\r
+register has three component data parts which record the amount of green,\r
+blue and red colours which make up the displayed colour. Each of these\r
+seperate data components can hold a value between 0 and 63 giving the VGA\r
+display card the ability to display 262,144 colours! Although only a\r
+small subset of them can be displayed at any one time.\r
+\r
+Setting the value of a DAC register is achieved by calling the BIOS\r
+display function 16 with a value of 16 stored in the assembly language AL\r
+register, the green value stored in the assembly language CH register,\r
+the blue value stored in the assembly language CL register and the red\r
+value stored in the assembly language DH register and the number of the\r
+DAC register to be set stored in the assembly language BX register;\r
+\r
+\r
+          mov   ah,16\r
+          mov   al,16\r
+          mov   ch,green\r
+          mov   cl,blue\r
+          mov   dh,red\r
+          mov   bx,dac\r
+          int   10h\r
+\r
+\r
+The C function to set a DAC register looks lik this;\r
+\r
+     void set_dac(int dac, unsigned char green, unsigned char blue,\r
+     unsigned char red)\r
+     {\r
+          union REGS regs;\r
+     \r
+          regs.h.ah = 16;\r
+          regs.h.al = 16;\r
+          regs.x.bx = dac;\r
+          regs.h.ch = green;\r
+          regs.h.cl = blue;\r
+          regs.h.dh = red;\r
+          int86(0x10,&regs,&regs);\r
+     }\r
+     \r
+\r
+\r
+Displaying Text\r
+\r
+The BIOS ROM provides three functions for displaying a single character.\r
+The first function to consider is the one used extensively by DOS for\r
+displaying messages, this is function 14 called "write text in teletype\r
+mode". This function interprets some control characters; bell (ascii 7),\r
+backspace (ascii 8), carriage return (ascii 10) and line feed (ascii 13)\r
+but all other ascii codes are displayed, and the current cursor position\r
+updated accordingly, moving down a row when a character is displayed in\r
+the far right column. To call this function the assembly language\r
+register AL holds the ascii code of the character to be displayed and\r
+assembly language register BL holds the foreground colour for the\r
+character to be displayed in if a graphics mode is active;\r
+\r
+\r
+          mov   ah,14\r
+          mov   al,character\r
+          mov   bh,0\r
+          mov   bl,foreground\r
+          int   10h\r
+\r
+\r
+A C function for accessing the write text in teletype mode may be written\r
+like this;\r
+\r
+     #include <dos.h>\r
+     \r
+     void teletype(unsigned char character, unsigned char foreground)\r
+     {\r
+          union REGS inreg, outreg;\r
+     \r
+          inreg.h.ah = 14;\r
+          inreg.h.al = character;\r
+          inreg.h.bh = 0;\r
+          inreg.h.bl = foreground;\r
+          int86(0x10,&inrg,&outreg);\r
+     }\r
+     \r
+The second BIOS ROM display function for displaying a character allows\r
+the foreground and background colours of the displayed character to be\r
+defined. It also allows multiple copies of the character to be displayed\r
+one after another automatically displaying subsequent characters at the\r
+next display position, although the current cursor position is not\r
+changed by this function.\r
+\r
+This function is called "write character and attribute", and is BIOS ROM\r
+display function number 9. It is called with the ascii code of the\r
+character to be displayed in the assembly language AL register, the\r
+display page in assembly language register BH, the foreground colour in\r
+the first four bits of the assembly language register BL and the\r
+background colour in the last four bits of the assembly language register\r
+BL, the number of times the character is to be displayed is stored in the\r
+assembly language CX register thus;\r
+\r
+\r
+          mov   ah,9\r
+          mov   al,character\r
+          mov   bh,0\r
+          mov   bl,foreground + 16 * background\r
+          mov   cx,number\r
+          int   10h\r
+\r
+And in C;\r
+\r
+     #include <dos.h>\r
+     \r
+     void char_attrib(unsigned char character, unsigned char foreground,\r
+     unsigned char background, int number)\r
+     {\r
+          union REGS inreg,outreg;\r
+     \r
+          inreg.h.ah = 9;\r
+          inreg.h.al = character;\r
+          inreg.h.bh = 0;\r
+          inreg.h.bl = (background << 4) + foreground;\r
+          inreg.x.cx = number;\r
+          int86(0x10,&inreg,&outreg);\r
+     }\r
+\r
+The last BIOS ROM display function for displaying a character retains the\r
+foreground and background colours of the display position.\r
+\r
+This function is called "write character", and is BIOS ROM display\r
+function number 10. It is identical to BIOS ROM display function 9 except\r
+that the colours of the displayed character are those which are prevalent\r
+at the display position, except in graphics modes when the foreground\r
+colour of the character is determined by the value in the assembly\r
+language BL register.  Its use is as follows;\r
+\r
+\r
+          mov   ah,10\r
+          mov   al,character\r
+          mov   bh,0\r
+          mov   bl,foreground   ; For graphics modes ONLY\r
+          mov   cx,number\r
+          int   10h\r
+\r
+And in C;\r
+\r
+     #include <dos.h>\r
+     \r
+     void char_attrib(unsigned char character, unsigned char foreground,\r
+     int number)\r
+     {\r
+          union REGS inreg,outreg;\r
+     \r
+          inreg.h.ah = 10;\r
+          inreg.h.al = character;\r
+          inreg.h.bh = 0;\r
+          inreg.h.bl = foreground; /* For graphics modes ONLY */\r
+          inreg.x.cx = number;\r
+          int86(0x10,&inreg,&outreg);\r
+     }\r
+     \r
+\r
+Positioning of the text cursor is provided for by the ROM BIOS display\r
+function number 2. It is called with the row number in the assembly\r
+language register DH and the column number in the assembly language\r
+register DL;\r
+\r
+          mov   ah,2\r
+          mov   bh,0\r
+          mov   dh,row\r
+          mov   dl,column\r
+          int   10h\r
+\r
+The corresponding function in C looks like this;\r
+\r
+     #include <dos.h>\r
+     \r
+     void at(unsigned char row, unsigned char column)\r
+     {\r
+          union REGS regs;\r
+     \r
+          regs.h.ah = 2;\r
+          regs.h.bh = 0;\r
+          regs.h.dh = row;\r
+          regs.h.dl = column;\r
+          int86(0x10,&regs,&regs);\r
+     }\r
+\r
+From these basic functions a more useful replacement for the C language's\r
+"printf()" function can be written which allows data to be displayed at\r
+the current cursor position, previously set by a call to "at()", with\r
+prescribed attributes;\r
+\r
+\r
+     #include <dos.h>\r
+     #include <stdarg.h>\r
+     \r
+     void at(unsigned char row, unsigned char column)\r
+     {\r
+          union REGS regs;\r
+     \r
+          regs.h.ah = 2;\r
+          regs.h.bh = 0;\r
+          regs.h.dh = row;\r
+          regs.h.dl = column;\r
+          int86(0x10,&regs,&regs);\r
+     }\r
+     \r
+     void xprintf(unsigned char foreground, unsigned char background,\r
+     char *format,...)\r
+     {\r
+          union REGS inreg,outreg;\r
+     \r
+          va_list arg_ptr;\r
+          static char output[1000];\r
+          unsigned char col;\r
+          unsigned char row;\r
+          unsigned char n;\r
+          unsigned char p;\r
+          unsigned char text;\r
+          unsigned char attr;\r
+     \r
+          /* Convert foreground and background colours into a single\r
+     attribute */\r
+          attr = (background << 4) + foreground;\r
+     \r
+          /* Copy data into a single string */\r
+          va_start(arg_ptr, format);\r
+          vsprintf(output, format, arg_ptr);\r
+     \r
+          /* Determine number of display columns */\r
+          inreg.h.ah = 15;\r
+          int86(0x10,&inreg,&outreg);\r
+          n = outreg.h.ah;\r
+     \r
+          /* Determine current cursor position */\r
+          inreg.h.ah = 3;\r
+          inreg.h.bh = 0;\r
+          int86(0x10,&inreg,&outreg);\r
+          row = outreg.h.dh;\r
+          col = outreg.h.dl;\r
+     \r
+          /* Now display data */\r
+          p = 0;\r
+          while (output[p])\r
+          {\r
+               /* Display this character */\r
+               inreg.h.bh = 0;\r
+               inreg.h.bl = attr;\r
+               inreg.x.cx = 01;\r
+               inreg.h.ah = 9;\r
+               inreg.h.al = output[p++];\r
+               int86(0x10,&inreg,&outreg);\r
+     \r
+               /* Update cursor position */\r
+               /* moving down a row if required */\r
+               col++;\r
+               if (col < (n - 1))\r
+                    at(row, col);\r
+               else\r
+               {\r
+                    col = 0;\r
+                    at(++row, col);\r
+               }\r
+          }\r
+     }\r
+     \r
+This function, "xprintf()" illustrates two more functions of the BIOS\r
+ROM. The first is the call to function 15 which returns the number of\r
+text display columns for the currently active display mode.\r
+\r
+The other function illustrated, but not yet discussed, is BIOS ROM\r
+function 3 which returns information about the cursor. The cursor's row\r
+is returned in the assembly language register DH, and it's column in the\r
+assembly language register DL.\r
+\r
+\r
+                                    \r
+                     ADVANCED GRAPHICS ON THE IBM PC\r
+\r
+This section aims to reveal more about the graphics facilities offered by\r
+the IBM PC, in particular topics which are of a more complex nature than\r
+those addressed in the previous section.\r
+\r
+\r
+\r
+Display Pages\r
+\r
+The information for display by the display card is stored in an area of\r
+memory called the "video RAM". The size of the video RAM varies from one\r
+display card to another, and the amount of video RAM required for a\r
+display varies with the selected display mode. Display modes which do not\r
+require all of the video RAM use the remaining video RAM for additional\r
+display pages.\r
+\r
+\r
+MODE               PAGES\r
+ 0                 8\r
+ 1                 8\r
+ 2                 4 (CGA) 8 (EGA, VGA)\r
+ 3                 4 (CGA) 8 (EGA, VGA)\r
+ 4                 1\r
+ 5                 1\r
+ 6                 1\r
+ 7                 8 (EGA, VGA)\r
+13                 8 (EGA, VGA)\r
+14                 4 (EGA, VGA)\r
+15                 2 (EGA, VGA)\r
+16                 2 (EGA, VGA)\r
+17                 1 (VGA)\r
+18                 1 (VGA)\r
+19                 1 (VGA)\r
+\r
+Many of the BIOS ROM display functions allow selection of the display\r
+page to be written to, regardless of which page is currently being\r
+displayed.\r
+\r
+The display card continuously updates the VDU from the information in the\r
+active display page. Changing the active display page instantaneously\r
+changes the display.\r
+\r
+This provides the graphics programmer with the means to build a display\r
+on an undisplayed page, and to then change the active display page so\r
+that the viewer does not see the display being drawn.\r
+\r
+Selection of the active display page is achieved by calling BIOS ROM\r
+display function 5 with the number of the required display page stored in\r
+the assembly language register AL;\r
+\r
+          mov   ah,5\r
+          mov   al,page\r
+          int   10h\r
+\r
+Or, from C this function becomes;\r
+\r
+     #include <dos.h>\r
+     \r
+     void set_page(unsigned char page)\r
+     {\r
+          union REGS inreg, outreg;\r
+     \r
+          inreg.h.ah = 5;\r
+          inreg.h.al = page;\r
+          int86(0x10,&inreg,&outreg);\r
+     }\r
+     \r
+The display page to which BIOS ROM display functions write is decided by\r
+the value stored in the assembly language BH register. The functions for\r
+setting a pixel's colour may be amended thus;\r
+\r
+          mov   ah, 12\r
+          mov   al, colour\r
+          mov   bh, page\r
+          mov   cx, x_coord\r
+          mov   dx, y_coord\r
+          int   10h\r
+\r
+And the corresponding C function becomes;\r
+\r
+     #include <dos.h>\r
+     \r
+     void plot(int x_coord, int y_coord, unsigned char colour, unsigned\r
+     char page)\r
+     {\r
+          /* Sets the colour of a pixel */\r
+     \r
+          union REGS inreg, outreg;\r
+     \r
+          inreg.h.ah = 12;\r
+          inreg.h.al = colour;\r
+          inreg.h.bh = page;\r
+          inreg.x.cx = x_coord;\r
+          inreg.x.dx = y_coord;\r
+          int86(0x10,&inreg,&outreg);\r
+     }\r
+\r
+The currently active display page can be determined by calling BIOS ROM\r
+display function 15. This function returns the active display page in the\r
+assembly language register BH;\r
+\r
+          mov   ah,15\r
+          int   10h\r
+                              ; BH now holds active page number\r
+\r
+                                    \r
+                         Advanced Text Routines\r
+\r
+When the IBM PC display is in a text mode a blinking cursor is displayed\r
+at the current cursor position. This cursor is formed of a rectangle\r
+which is one complete character width, but it's top and bottom pixel\r
+lines are definable within the limits of the character height by calling\r
+BIOS ROM display function 1. A CGA display has a character height of 8\r
+pixel lines, an EGA display has a character height of 14 lines and a VGA\r
+display has a character height of 16 lines.\r
+\r
+BIOS ROM function 1 is called with the top pixel line number of the\r
+desired cursor shape in assembly language register CH and the bottom\r
+pixel line number in assembly language register CL;\r
+\r
+\r
+          mov   ah,1\r
+          mov   ch,top\r
+          mov   cl,bottom\r
+          int   10h\r
+\r
+A C function to set the cursor shape may be be written thus;\r
+\r
+     #include <dos.h>\r
+     \r
+     void setcursor(unsigned char top, unsigned char bottom)\r
+     {\r
+          union REGS inreg, outreg;\r
+     \r
+          inreg.h.ch = start;\r
+          inreg.h.cl = end;\r
+          inreg.h.ah = 1;\r
+          int86(0x10, &inreg, &outreg);\r
+     }\r
+     \r
+If the top pixel line is defined as larger than the bottom line, then the\r
+cursor will appear as a pair of parallel rectangles.\r
+\r
+The cursor may be removed from view by calling BIOS ROM display function\r
+1 with a value of 32 in the assembly language CH register.\r
+\r
+The current shape of the cursor can be determined by calling BIOS ROM\r
+function 3, which returns the top pixel line number of the cursor shape\r
+in the assembly language CH register, and the bottom line number in the\r
+assembly language register CL.\r
+\r
+Two functions are provided by the BIOS ROM for scrolling of the currently\r
+active display page. These are function 6, which scrolls the display up\r
+and function 7 which scrolls the display down.\r
+\r
+Both functions accept the same parameters, these being the number of\r
+lines to scroll in the assembly language register AL, the colour\r
+attribute for the resulting blank line in the assembly language BH\r
+register, the top row of the area to be scrolled in the assembly language\r
+CH register, the left column of the area to be scrolled in the assembly\r
+language CL register, the bottom row to be scrolled in the assembly\r
+language DH register and the right most column to be scrolled in the\r
+assembly language DL register.\r
+\r
+If the number of lines being scrolled is greater than the number of lines\r
+in the specified area, then the result is to clear the specified area,\r
+filling it with spaces in the attribute specified in the assembly\r
+language BH register.\r
+\r
+\r
+Scrolling\r
+\r
+A C function to scroll the entire screen down one line can be written\r
+thus;\r
+\r
+     #include <dos.h>\r
+     \r
+     void scroll_down(unsigned char attr)\r
+     {\r
+          union REGS inreg, outreg;\r
+     \r
+          inreg.h.al = 1;\r
+          inreg.h.cl = 0;\r
+          inreg.h.ch = 0;\r
+          inreg.h.dl = 79;\r
+          inreg.h.dh = 24;    /* Assuming a 25 line display */\r
+          inreg.h.bh = attr;\r
+          inreg.h.ah = 7;\r
+          int86(0x10, &inreg, &outreg);\r
+     }\r
+\r
+\r
+Clear Screen\r
+A simple clear screen function can be written in C based upon the\r
+"scroll_down()" function simply by changing the value assigned to\r
+inreg.h.al to 0;\r
+\r
+     #include <dos.h>\r
+     \r
+     void cls(unsigned char attr)\r
+     {\r
+          union REGS inreg, outreg;\r
+     \r
+          inreg.h.al = 0;\r
+          inreg.h.cl = 0;\r
+          inreg.h.ch = 0;\r
+          inreg.h.dl = 79;\r
+          inreg.h.dh = 24;    /* Assuming a 25 line display */\r
+          inreg.h.bh = attr;\r
+          inreg.h.ah = 7;\r
+          int86(0x10, &inreg, &outreg);\r
+     }\r
+\r
+\r
+\r
+Windowing\r
+Windowing functions need to preserve the display they overwrite, and\r
+restore it when the window is removed from display. The BIOS ROM provides\r
+a display function which enables this to be done.\r
+\r
+Function 8 requires the appropriate display page number to be stored in\r
+assembly language register BH, and then when called it returns the ascii\r
+code of the character at the current cursor position of that display page\r
+in the assembly language AL register, and the display attribute of the\r
+character in the assembly language AH register.\r
+\r
+The following C functions allow an area of the display to be preserved,\r
+and later restored;\r
+\r
+     #include <dos.h>\r
+     \r
+     void at(unsigned char row, unsigned char column, unsigned char page)\r
+     {\r
+          /* Position the cursor */\r
+     \r
+          union REGS inreg,outreg;\r
+     \r
+          inreg.h.ah = 2;\r
+          inreg.h.bh = page;\r
+          inreg.h.dh = row;\r
+          inreg.h.dl = column;\r
+          int86(0x10,&inreg,&outreg);\r
+     }\r
+     \r
+     void get_win(unsigned char left, unsigned char top, unsigned char\r
+     right,unsigned char bottom, unsigned char page,        char *buffer)\r
+     {\r
+          /* Read a text window into a variable */\r
+     \r
+          union REGS inreg,outreg;\r
+     \r
+          unsigned char old_left;\r
+          unsigned char old_row;\r
+          unsigned char old_col;\r
+     \r
+          /* save current cursor position */\r
+          inreg.h.ah = 3;\r
+          inreg.h.bh = page;\r
+          int86(0x10,&inreg,&outreg);\r
+          old_row = outreg.h.dh;\r
+          old_col = outreg.h.dl;\r
+     \r
+          while(top <= bottom)\r
+          {\r
+               old_left = left;\r
+               while(left <= right)\r
+               {\r
+                    at(top,left,page);\r
+                    inreg.h.bh = page;\r
+                    inreg.h.ah = 8;\r
+                    int86(0x10,&inreg,&outreg);\r
+                    *buffer++ = outreg.h.al;\r
+                    *buffer++ = outreg.h.ah;\r
+                    left++;\r
+               }\r
+     \r
+               left = old_left;\r
+               top++;\r
+          }\r
+     \r
+          /* Restore cursor to original location */\r
+          at(old_row,old_col,page);\r
+     }\r
+     \r
+     void put_win(unsigned char left, unsigned char top, unsigned char\r
+     right,  unsigned char bottom, unsigned char            page, char\r
+     *buffer)\r
+     {\r
+          /* Display a text window from a variable */\r
+     \r
+          union REGS inreg,outreg;\r
+     \r
+          unsigned char old_left;\r
+          unsigned char chr;\r
+          unsigned char attr;\r
+          unsigned char old_row;\r
+          unsigned char old_col;\r
+     \r
+          /* save current cursor position */\r
+          inreg.h.ah = 3;\r
+          inreg.h.bh = page;\r
+          int86(0x10,&inreg,&outreg);\r
+          old_row = outreg.h.dh;\r
+          old_col = outreg.h.dl;\r
+     \r
+          while(top <= bottom)\r
+          {\r
+               old_left = left;\r
+               while(left <= right)\r
+               {\r
+                    at(top,left,page);\r
+                    chr = *buffer++;\r
+                    attr = *buffer++;\r
+                    inreg.h.bh = page;\r
+                    inreg.h.ah = 9;\r
+                    inreg.h.al = chr;\r
+                    inreg.h.bl = attr;\r
+                    inreg.x.cx = 1;\r
+                    int86(0x10,&inreg,&outreg);\r
+                    left++;\r
+               }\r
+               left = old_left;\r
+               top++;\r
+          }\r
+     \r
+          /* Restore cursor to original location */\r
+          at(old_row,old_col,page);\r
+     }\r
+                                    \r
+                   DIRECT VIDEO ACCESS WITH THE IBM PC\r
+\r
+Accessing video RAM directly is much faster than using the BIOS ROM\r
+display functions. There are problems however. Different video modes\r
+arrange their use of video RAM in different ways so a number of functions\r
+are required for plotting using direct video access, where as only one\r
+function is required if use is made of the BIOS ROM display function.\r
+\r
+The following C function will set a pixel in CGA display modes 4 and 5\r
+directly;\r
+\r
+\r
+     void dplot4(int y, int x, int colour)\r
+     {\r
+          /* Direct plotting in modes 4 & 5 ONLY! */\r
+     \r
+          union mask\r
+          {\r
+               char c[2];\r
+               int i;\r
+          }bit_mask;\r
+     \r
+          int index;\r
+          int bit_position;\r
+     \r
+          unsigned char t;\r
+          char xor;\r
+     \r
+          char far *ptr = (char far *) 0xB8000000;\r
+     \r
+          bit_mask.i = 0xFF3F;\r
+     \r
+          if ( y < 0 || y > 319 || x < 0 || x > 199)\r
+               return;\r
+     \r
+          xor = colour & 128;\r
+     \r
+          colour = colour & 127;\r
+     \r
+          bit_position = y % 4;\r
+     \r
+          colour <<= 2 * (3 - bit_position);\r
+     \r
+          bit_mask.i >>= 2 * bit_position;\r
+     \r
+          index = x * 40 + (y / 4);\r
+     \r
+          if (x % 2)\r
+               index += 8152;\r
+     \r
+     \r
+     \r
+     \r
+          if (!xor)\r
+          {\r
+               t = *(ptr + index) & bit_mask.c[0];\r
+               *(ptr + index) = t | colour;\r
+          }\r
+          else\r
+          {\r
+               t = *(ptr + index) | (char)0;\r
+               *(ptr + index) = t ^ colour;\r
+          }\r
+     }\r
+     \r
+\r
+Direct plotting in VGA mode 19 is very much simpler;\r
+\r
+     void dplot19(int x, int y, unsigned char colour)\r
+     {\r
+          /* Direct plot in mode 19 ONLY */\r
+          char far *video;\r
+     \r
+          video = MK_FP(0xA000,0);\r
+          video[x + y * 320] = colour;\r
+}\r
+                                    \r
+              ADVANCED GRAPHICS TECHNIQUES WITH THE IBM PC\r
+\r
+\r
+\r
+\r
+Increasing Colours\r
+\r
+The EGA display is limited displaying a maximum of 16 colours, however in\r
+high resolution graphics modes (such as mode 16) the small physical size\r
+of the pixels allows blending of adjacent colours to produce additional\r
+shades.\r
+\r
+If a line is drawn straight across a black background display in blue,\r
+and then a subsequent line is drawn beneath it also in blue but only\r
+plotting alternate pixels, the second line will appear in a darker shade\r
+of the same colour.\r
+\r
+The following C program illustrates this idea;\r
+\r
+\r
+     #include <dos.h>\r
+     \r
+     union REGS inreg, outreg;\r
+     \r
+     void setvideo(unsigned char mode)\r
+     {\r
+          /* Sets the video display mode */\r
+     \r
+          inreg.h.al = mode;\r
+          inreg.h.ah = 0;\r
+          int86(0x10, &inreg, &outreg);\r
+     }\r
+     \r
+     void plot(int x, int y, unsigned char colour)\r
+     {\r
+          /* Sets a pixel at the specified coordinates */\r
+     \r
+          inreg.h.al = colour;\r
+          inreg.h.bh = 0;\r
+          inreg.x.cx = x;\r
+          inreg.x.dx = y;\r
+          inreg.h.ah = 0x0C;\r
+          int86(0x10, &inreg, &outreg);\r
+     }\r
+     \r
+     void line(int a, int b, int c, int d, unsigned char colour)\r
+     {\r
+          /* Draws a straight line from (a,b) to (c,d) */\r
+     \r
+          int u;\r
+          int v;\r
+          int d1x;\r
+          int d1y;\r
+          int d2x;\r
+          int d2y;\r
+          int m;\r
+          int n;\r
+          int s;\r
+          int i;\r
+     \r
+          u = c - a;\r
+          v = d - b;\r
+          if (u == 0)\r
+          {\r
+               d1x = 0;\r
+               m = 0;\r
+          }\r
+          else\r
+          {\r
+               m = abs(u);\r
+               if (u < 0)\r
+                    d1x = -1;\r
+               else\r
+               if (u > 0)\r
+                    d1x = 1;\r
+          }\r
+          if ( v == 0)\r
+          {\r
+               d1y = 0;\r
+               n = 0;\r
+          }\r
+          else\r
+          {\r
+               n = abs(v);\r
+               if (v < 0)\r
+                    d1y = -1;\r
+               else\r
+               if (v > 0)\r
+                    d1y = 1;\r
+          }\r
+          if (m > n)\r
+          {\r
+               d2x = d1x;\r
+               d2y = 0;\r
+          }\r
+          else\r
+          {\r
+               d2x = 0;\r
+               d2y = d1y;\r
+               m = n;\r
+               n = abs(u);\r
+          }\r
+          s = m / 2;\r
+     \r
+          for (i = 0; i <= m; i++)\r
+          {\r
+               plot(a,b,colour);\r
+               s += n;\r
+               if (s >= m)\r
+               {\r
+                    s -= m;\r
+                    a += d1x;\r
+                    b += d1y;\r
+               }\r
+               else\r
+               {\r
+                    a += d2x;\r
+                    b += d2y;\r
+               }\r
+          }\r
+     }\r
+     \r
+     void dot_line(int a, int b, int c, int d, int colour)\r
+     {\r
+          /* Draws a dotted straight line from (a,b) to (c,d) */\r
+     \r
+          int u;\r
+          int v;\r
+          int d1x;\r
+          int d1y;\r
+          int d2x;\r
+          int d2y;\r
+          int m;\r
+          int n;\r
+          int s;\r
+          int i;\r
+     \r
+          u = c - a;\r
+          v = d - b;\r
+          if (u == 0)\r
+          {\r
+               d1x = 0;\r
+               m = 0;\r
+          }\r
+          else\r
+          {\r
+               m = abs(u);\r
+               if (u < 0)\r
+                    d1x = -2;\r
+               else\r
+               if (u > 0)\r
+                    d1x = 2;\r
+          }\r
+          if (v == 0)\r
+          {\r
+               d1y = 0;\r
+               n = 0;\r
+          }\r
+          else\r
+          {\r
+               n = abs(v);\r
+               if (v < 0)\r
+                    d1y = -2;\r
+               else\r
+               if (v > 0)\r
+                    d1y = 2;\r
+          }\r
+          if (m > n)\r
+          {\r
+               d2x = d1x;\r
+               d2y = 0;\r
+          }\r
+          else\r
+          {\r
+               d2x = 0;\r
+               d2y = d1y;\r
+               m = n;\r
+               n = abs(u);\r
+          }\r
+          s = m / 2;\r
+     \r
+          for (i = 0; i <= m; i+=2)\r
+          {\r
+               plot(a,b,colour);\r
+               s += n;\r
+               if (s >= m)\r
+               {\r
+                    s -= m;\r
+                    a += d1x;\r
+                    b += d1y;\r
+               }\r
+               else\r
+               {\r
+                    a += d2x;\r
+                    b += d2y;\r
+               }\r
+          }\r
+     }\r
+     \r
+     \r
+     void main(void)\r
+     {\r
+          int n;\r
+     \r
+          /* Display different colour bands */\r
+     \r
+          setvideo(16);\r
+     \r
+          for(n = 1; n < 16; n++)\r
+          {\r
+               line(0,n * 20,639,n * 20,n);\r
+               line(0,1 + n * 20,639,1 + n * 20,n);\r
+               line(0,2 + n * 20,639,2 + n * 20,n);\r
+     \r
+               dot_line(0,4 + n * 20,639,4 + n * 20,n);\r
+               dot_line(1,5 + n * 20,639,5 + n * 20,n);\r
+               dot_line(0,6 + n * 20,639,6 + n * 20,n);\r
+     \r
+               dot_line(0,8 + n * 20,639,8 + n * 20,n);\r
+               dot_line(1,9 + n * 20,639,9 + n * 20,n);\r
+               dot_line(0,10 + n * 20,639,10 + n * 20,n);\r
+     \r
+               dot_line(1,8 + n * 20,639,8 + n * 20,7);\r
+               dot_line(0,9 + n * 20,639,9 + n * 20,7);\r
+               dot_line(1,10 + n * 20,639,10 + n * 20,7);\r
+     \r
+               dot_line(1,12 + n * 20,639,12 + n * 20,n);\r
+               dot_line(0,13 + n * 20,639,13 + n * 20,n);\r
+               dot_line(1,14 + n * 20,639,14 + n * 20,n);\r
+     \r
+               dot_line(0,12 + n * 20,639,12 + n * 20,14);\r
+               dot_line(1,13 + n * 20,639,13 + n * 20,14);\r
+               dot_line(0,14 + n * 20,639,14 + n * 20,14);\r
+          }\r
+     }\r
+\r
+This technique can be put to good use for drawing three dimensional\r
+boxes;\r
+\r
+     void box3d(int xa,int ya, int xb, int yb, int col)\r
+     {\r
+          /* Draws a box for use in 3d histogram graphs etc */\r
+     \r
+          int xc;\r
+          int xd;\r
+     \r
+          int n;\r
+     \r
+          xd = (xb - xa) / 2;\r
+          xc = xa + xd;\r
+     \r
+          /* First draw the solid face */\r
+          for(n = xa; n < xb; n++)\r
+               line(n,ya,n,yb,col);\r
+     \r
+          /* Now "shaded" top and side */\r
+          for(n = 0; n < xd; n++)\r
+          {\r
+               dotline(xa + n,yb - n ,xc + n,yb - n,col);\r
+               dotline(xa + xd + n,yb - n ,xc + xd + n,yb - n,col);\r
+               dotline(xb +n ,ya - n ,xb + n,yb - n,col);\r
+          }\r
+     }\r
+     \r
+\r
+\r
+Displaying Text At Pixel Coordinates\r
+\r
+When using graphics display modes it is useful to be able to display text\r
+not at the fixed character boundaries, but at pixel coordinates. This can\r
+be achieved by implementing a print function which reads the character\r
+definition data from the BIOS ROM and uses it to plot pixels to create\r
+the shapes of the desired text. The following C function, "gr_print()"\r
+illustrates this idea using the ROM CGA (8x8) character set;\r
+\r
+     void gr_print(char *output, int x, int y, unsigned char colour)\r
+     {\r
+          unsigned char far *ptr;\r
+          unsigned char chr;\r
+          unsigned char bmask;\r
+          int i;\r
+          int k;\r
+          int oldy;\r
+          int p;\r
+          int height;\r
+     \r
+          /* The height of the characters in the font being accessed */\r
+          height = 8;\r
+     \r
+          /* Set pointer to start of font definition in the ROM */\r
+          ptr = getfontptr(3);\r
+     \r
+          oldy = y;\r
+          p = 0;\r
+     \r
+          /* Loop output string */\r
+          while(output[p])\r
+          {\r
+               /* Get first character to be displayed */\r
+               chr = output[p];\r
+     \r
+               /* Loop pixel lines in character definition */\r
+               for(i = 0; i < height; i++)\r
+               {\r
+                    /* Get pixel line definition from the ROM */\r
+                    bmask = *(ptr + (chr * height) + i);\r
+     \r
+                    /* Loop pixel columns */\r
+                    for (k = 0; k < 8; ++k, bmask <<= 1)\r
+                    {\r
+                         /* Test for a set bit */\r
+                         if(bmask & 128)\r
+                              /* Plot a pixel if appropriate */\r
+                              plot(x,y,colour);\r
+                         else\r
+                              plot(x,y,0);\r
+                         x++;\r
+                    }\r
+                    /* Down to next row and left to start of character */\r
+                    y++;\r
+                    x -= 8;\r
+               }\r
+               /* Next character to be displayed */\r
+               p++;\r
+     \r
+               /* Back to top row of the display position */\r
+               y = oldy;\r
+     \r
+               /* Right to next character display position */\r
+               x += 8;\r
+          }\r
+     }\r
+\r
+The following assembly language support function is required to retrieve\r
+the address of the ROM font by calling the BIOS ROM display function\r
+which will return the address;\r
+\r
+\r
+     ; GET FONT POINTER\r
+     ; Small memory model\r
+     ; compile with tasm /mx\r
+     \r
+     _TEXT     segment   byte public 'CODE'\r
+                     assume   cs:_TEXT,ds:NOTHING\r
+     \r
+     _getfontptr    proc near\r
+               push bp\r
+               mov   bp,sp\r
+               mov   ax,1130h\r
+               mov   bh, [bp+4]         ; Number for font to be retrieved\r
+               int   10h\r
+               mov   dx,es\r
+               mov   ax,bp\r
+               pop   bp\r
+               ret\r
+     _getfontptr    endp\r
+     \r
+     _TEXT     ends\r
+     \r
+          public    _getfontptr\r
+          end\r
+     \r
+The font number supplied to "getfontptr()" can be one of;\r
+\r
+     2    ROM EGA 8 x 14 font\r
+     3    ROM CGA 8 x 8 font\r
+     6    ROM VGA 8 x 16 font\r
+\r
+A Graphics Function Library For Turbo C\r
+\r
+     /* Graphics library for 'Turbo C'  (V2.01) */\r
+     \r
+     /* (C)1992 Copyright Servile Software */\r
+     \r
+     #include <stdio.h>\r
+     #include <dos.h>\r
+     #include <stdlib.h>\r
+     #include <stdarg.h>\r
+     #include <string.h>\r
+     #include <math.h>\r
+     \r
+     /* Global variables */\r
+     static unsigned char attribute;\r
+     int _dmode;\r
+     int _lastx;\r
+     int _lasty;\r
+     \r
+     /* Maximum coordinates for graphics screen */\r
+     static int maxx;\r
+     static int maxy;\r
+     \r
+     /* Sprite structure */\r
+     struct SP\r
+     {\r
+          int x;\r
+          int y;\r
+          char data[256];\r
+          char save[256];\r
+     };\r
+     typedef struct SP SPRITE;\r
+     \r
+     \r
+     /* Sine and cosine tables for trig' operations */\r
+     \r
+     static double sintable[360] =\r
+     {0.000000000000000001,0.017452406437283512,\r
+                    0.034899496702500969,0.052335956242943835,\r
+                    0.069756473744125302,0.087155742747658166,\r
+                    0.104528463267653457,0.121869343405147462,\r
+                    0.139173100960065438,0.156434465040230869,\r
+                    0.173648177666930331,0.190808995376544804,\r
+                    0.207911690817759315,0.224951054343864976,\r
+                    0.241921895599667702,0.258819045102520739,\r
+                    0.275637355816999163,0.292371704722736769,\r
+                    0.309016994374947451,0.325568154457156755,\r
+                    0.342020143325668824,0.358367949545300379,\r
+                    0.374606593415912181,0.390731128489273882,\r
+                    0.406736643075800375,0.422618261740699608,\r
+                    0.438371146789077626,0.453990499739547027,\r
+                    0.469471562785891028,0.484809620246337225,\r
+                    0.500000000000000222,0.515038074910054489,\r
+                    0.529919264233205234,0.544639035015027417,\r
+                    0.559192903470747127,0.573576436351046381,\r
+                    0.587785252292473470,0.601815023152048600,\r
+                    0.615661475325658625,0.629320391049837835,\r
+                    0.642787609686539696,0.656059028990507720,\r
+                    0.669130606358858682,0.681998360062498921,\r
+                    0.694658370458997698,0.707106781186548017,\r
+                    0.719339800338651636,0.731353701619170904,\r
+                    0.743144825477394688,0.754709580222772458,\r
+                    0.766044443118978569,0.777145961456971346,\r
+                    0.788010753606722458,0.798635510047293384,\r
+                    0.809016994374947895,0.819152044288992243,\r
+                    0.829037572555042179,0.838670567945424494,\r
+                    0.848048096156426512,0.857167300702112778,\r
+                    0.866025403784439152,0.874619707139396296,\r
+                    0.882947592858927432,0.891006524188368343,\r
+                    0.898794046299167482,0.906307787036650381,\r
+                    0.913545457642601311,0.920504853452440819,\r
+                    0.927183854566787868,0.933580426497202187,\r
+                    0.939692620785908761,0.945518575599317179,\r
+                    0.951056516295153975,0.956304755963035880,\r
+                    0.961261695938319227,0.965925826289068645,\r
+                    0.970295726275996806,0.974370064785235579,\r
+                    0.978147600733805911,0.981627183447664198,\r
+                    0.984807753012208353,0.987688340595137992,\r
+                    0.990268068741570473,0.992546151641322205,\r
+                    0.994521895368273512,0.996194698091745656,\r
+                    0.997564050259824309,0.998629534754573944,\r
+                    0.999390827019095762,0.999847695156391270,\r
+                    1.000000000000000000,0.999847695156391159,\r
+                    0.999390827019095651,0.998629534754573833,\r
+                    0.997564050259824087,0.996194698091745323,\r
+                    0.994521895368273179,0.992546151641321761,\r
+                    0.990268068741570029,0.987688340595137437,\r
+                    0.984807753012207687,0.981627183447663532,\r
+                    0.978147600733805245,0.974370064785234802,\r
+                    0.970295726275996029,0.965925826289067757,\r
+                    0.961261695938318228,0.956304755963034880,\r
+                    0.951056516295152865,0.945518575599316069,\r
+                    0.939692620785907651,0.933580426497200966,\r
+                    0.927183854566786536,0.920504853452439486,\r
+                    0.913545457642599978,0.906307787036649048,\r
+                    0.898794046299166149,0.891006524188367122,\r
+                    0.882947592858926211,0.874619707139395186,\r
+                    0.866025403784438042,0.857167300702111778,\r
+                    0.848048096156425624,0.838670567945423717,\r
+                    0.829037572555041513,0.819152044288991688,\r
+                    0.809016994374947451,0.798635510047293051,\r
+                    0.788010753606722236,0.777145961456971346,\r
+                    0.766044443118978569,0.754709580222772680,\r
+                    0.743144825477395132,0.731353701619171459,\r
+                    0.719339800338652302,0.707106781186548794,\r
+                    0.694658370458998808,0.681998360062500142,\r
+                    0.669130606358860014,0.656059028990509274,\r
+                    0.642787609686541472,0.629320391049839833,\r
+                    0.615661475325660845,0.601815023152051043,\r
+                    0.587785252292476135,0.573576436351049268,\r
+                    0.559192903470750236,0.544639035015030637,\r
+                    0.529919264233208676,0.515038074910058152,\r
+                    0.500000000000004219,0.484809620246341444,\r
+                    0.469471562785895413,0.453990499739551634,\r
+                    0.438371146789082455,0.422618261740704715,\r
+                    0.406736643075805704,0.390731128489279489,\r
+                    0.374606593415918010,0.358367949545306430,\r
+                    0.342020143325675152,0.325568154457163306,\r
+                    0.309016994374954279,0.292371704722743819,\r
+                    0.275637355817006491,0.258819045102528289,\r
+                    0.241921895599675502,0.224951054343872997,\r
+                    0.207911690817767558,0.190808995376553270,\r
+                    0.173648177666939019,0.156434465040239751,\r
+                    0.139173100960074542,0.121869343405156802,\r
+                    0.104528463267663005,0.087155742747667922,\r
+                    0.069756473744135267,0.052335956242954007,\r
+                    0.034899496702511350,0.017452406437294093,\r
+                    0.000000000000010781,-0.017452406437272534,\r
+                    -0.034899496702489805,-0.052335956242932476,\r
+                    -0.069756473744113756,-0.087155742747646453,\r
+                    -0.104528463267641564,-0.121869343405135402,\r
+                    -0.139173100960053198,-0.156434465040218462,\r
+                    -0.173648177666917786,-0.190808995376532092,\r
+                    -0.207911690817746464,-0.224951054343851986,\r
+                    -0.241921895599654574,-0.258819045102507472,\r
+                    -0.275637355816985785,-0.292371704722723225,\r
+                    -0.309016994374933796,-0.325568154457142933,\r
+                    -0.342020143325654891,-0.358367949545286335,\r
+                    -0.374606593415898026,-0.390731128489259616,\r
+                    -0.406736643075785997,-0.422618261740685175,\r
+                    -0.438371146789063082,-0.453990499739532427,\r
+                    -0.469471562785876373,-0.484809620246322570,\r
+                    -0.499999999999985512,-0.515038074910039723,\r
+                    -0.529919264233190468,-0.544639035015012540,\r
+                    -0.559192903470732361,-0.573576436351031616,\r
+                    -0.587785252292458593,-0.601815023152033834,\r
+                    -0.615661475325643859,-0.629320391049823069,\r
+                    -0.642787609686525041,-0.656059028990493065,\r
+                    -0.669130606358844027,-0.681998360062484377,\r
+                    -0.694658370458983265,-0.707106781186533584,\r
+                    -0.719339800338637314,-0.731353701619156804,\r
+                    -0.743144825477380699,-0.754709580222758580,\r
+                    -0.766044443118964691,-0.777145961456957690,\r
+                    -0.788010753606709025,-0.798635510047280062,\r
+                    -0.809016994374934795,-0.819152044288979364,\r
+                    -0.829037572555029412,-0.838670567945412060,\r
+                    -0.848048096156414188,-0.857167300702100676,\r
+                    -0.866025403784427272,-0.874619707139384750,\r
+                    -0.882947592858916108,-0.891006524188357352,\r
+                    -0.898794046299156713,-0.906307787036639945,\r
+                    -0.913545457642591208,-0.920504853452430938,\r
+                    -0.927183854566778320,-0.933580426497192972,\r
+                    -0.939692620785899990,-0.945518575599308742,\r
+                    -0.951056516295145871,-0.956304755963028108,\r
+                    -0.961261695938311900,-0.965925826289061651,\r
+                    -0.970295726275990256,-0.974370064785229362,\r
+                    -0.978147600733800138,-0.981627183447658869,\r
+                    -0.984807753012203468,-0.987688340595133552,\r
+                    -0.990268068741566587,-0.992546151641318764,\r
+                    -0.994521895368270514,-0.996194698091743103,\r
+                    -0.997564050259822310,-0.998629534754572390,\r
+                    -0.999390827019094763,-0.999847695156390714,\r
+                    -1.000000000000000000,-0.999847695156391714,\r
+                    -0.999390827019096761,-0.998629534754575388,\r
+                    -0.997564050259826307,-0.996194698091748099,\r
+                    -0.994521895368276398,-0.992546151641325647,\r
+                    -0.990268068741574470,-0.987688340595142433,\r
+                    -0.984807753012213349,-0.981627183447669860,\r
+                    -0.978147600733812128,-0.974370064785242240,\r
+                    -0.970295726276004022,-0.965925826289076417,\r
+                    -0.961261695938327665,-0.956304755963044872,\r
+                    -0.951056516295163523,-0.945518575599327393,\r
+                    -0.939692620785919530,-0.933580426497213511,\r
+                    -0.927183854566799748,-0.920504853452453253,\r
+                    -0.913545457642614411,-0.906307787036664148,\r
+                    -0.898794046299181804,-0.891006524188383331,\r
+                    -0.882947592858942976,-0.874619707139412506,\r
+                    -0.866025403784455916,-0.857167300702130208,\r
+                    -0.848048096156444498,-0.838670567945443146,\r
+                    -0.829037572555061497,-0.819152044289012227,\r
+                    -0.809016994374968434,-0.798635510047314479,\r
+                    -0.788010753606744219,-0.777145961456993772,\r
+                    -0.766044443119001550,-0.754709580222796106,\r
+                    -0.743144825477418891,-0.731353701619195773,\r
+                    -0.719339800338677060,-0.707106781186574107,\r
+                    -0.694658370459024455,-0.681998360062526232,\r
+                    -0.669130606358886548,-0.656059028990536253,\r
+                    -0.642787609686568784,-0.629320391049867478,\r
+                    -0.615661475325688934,-0.601815023152079465,\r
+                    -0.587785252292504890,-0.573576436351078467,\r
+                    -0.559192903470779767,-0.544639035015060502,\r
+                    -0.529919264233238985,-0.515038074910088794,\r
+                    -0.500000000000035083,-0.484809620246372641,\r
+                    -0.469471562785926888,-0.453990499739583386,\r
+                    -0.438371146789114541,-0.422618261740737022,\r
+                    -0.406736643075838289,-0.390731128489312296,\r
+                    -0.374606593415951039,-0.358367949545339737,\r
+                    -0.342020143325708625,-0.325568154457197001,\r
+                    -0.309016994374988196,-0.292371704722777903,\r
+                    -0.275637355817040797,-0.258819045102562761,\r
+                    -0.241921895599710085,-0.224951054343907719,\r
+                    -0.207911690817802419,-0.190808995376588242,\r
+                    -0.173648177666974129,-0.156434465040274973,\r
+                    -0.139173100960109847,-0.121869343405192190,\r
+                    -0.104528463267698463,-0.087155742747703435,\r
+                    -0.069756473744170822,-0.052335956242989604,\r
+                    -0.034899496702546981,-0.017452406437329739\r
+          };\r
+\r
+static double costable[360] = {\r
+1.000000000000000000,0.999847695156391270,\r
+                    0.999390827019095762,0.998629534754573833,\r
+                    0.997564050259824198,0.996194698091745545,\r
+                    0.994521895368273290,0.992546151641321983,\r
+                    0.990268068741570362,0.987688340595137770,\r
+                    0.984807753012208020,0.981627183447663976,\r
+                    0.978147600733805689,0.974370064785235246,\r
+                    0.970295726275996473,0.965925826289068312,\r
+                    0.961261695938318894,0.956304755963035436,\r
+                    0.951056516295153531,0.945518575599316735,\r
+                    0.939692620785908317,0.933580426497201743,\r
+                    0.927183854566787313,0.920504853452440264,\r
+                    0.913545457642600867,0.906307787036649826,\r
+                    0.898794046299166927,0.891006524188367788,\r
+                    0.882947592858926766,0.874619707139395630,\r
+                    0.866025403784438486,0.857167300702112112,\r
+                    0.848048096156425846,0.838670567945423828,\r
+                    0.829037572555041513,0.819152044288991576,\r
+                    0.809016994374947229,0.798635510047292607,\r
+                    0.788010753606721681,0.777145961456970569,\r
+                    0.766044443118977680,0.754709580222771681,\r
+                    0.743144825477393911,0.731353701619170127,\r
+                    0.719339800338650748,0.707106781186547129,\r
+                    0.694658370458996810,0.681998360062498032,\r
+                    0.669130606358857682,0.656059028990506721,\r
+                    0.642787609686538697,0.629320391049836836,\r
+                    0.615661475325657626,0.601815023152047601,\r
+                    0.587785252292472471,0.573576436351045382,\r
+                    0.559192903470746128,0.544639035015026307,\r
+                    0.529919264233204124,0.515038074910053378,\r
+                    0.499999999999999112,0.484809620246336115,\r
+                    0.469471562785889862,0.453990499739545861,\r
+                    0.438371146789076460,0.422618261740698442,\r
+                    0.406736643075799154,0.390731128489272661,\r
+                    0.374606593415910960,0.358367949545299158,\r
+                    0.342020143325667547,0.325568154457155479,\r
+                    0.309016994374946230,0.292371704722735493,\r
+                    0.275637355816997887,0.258819045102519463,\r
+                    0.241921895599666398,0.224951054343863643,\r
+                    0.207911690817757955,0.190808995376543389,\r
+                    0.173648177666928888,0.156434465040229398,\r
+                    0.139173100960063939,0.121869343405145950,\r
+                    0.104528463267651903,0.087155742747656584,\r
+                    0.069756473744123679,0.052335956242942190,\r
+                    0.034899496702499304,0.017452406437281822,\r
+                    -0.000000000000001715,-0.017452406437285253,\r
+                    -0.034899496702502732,-0.052335956242945618,\r
+                    -0.069756473744127107,-0.087155742747659998,\r
+                    -0.104528463267655317,-0.121869343405149350,\r
+                    -0.139173100960067325,-0.156434465040232784,\r
+                    -0.173648177666932274,-0.190808995376546747,\r
+                    -0.207911690817761285,-0.224951054343866974,\r
+                    -0.241921895599669701,-0.258819045102522793,\r
+                    -0.275637355817001217,-0.292371704722738768,\r
+                    -0.309016994374949450,-0.325568154457158698,\r
+                    -0.342020143325670822,-0.358367949545302322,\r
+                    -0.374606593415914124,-0.390731128489275825,\r
+                    -0.406736643075802318,-0.422618261740701329,\r
+                    -0.438371146789079125,-0.453990499739548303,\r
+                    -0.469471562785892083,-0.484809620246338169,\r
+                    -0.500000000000000999,-0.515038074910054933,\r
+                    -0.529919264233205567,-0.544639035015027528,\r
+                    -0.559192903470747127,-0.573576436351046159,\r
+                    -0.587785252292473026,-0.601815023152048045,\r
+                    -0.615661475325657848,-0.629320391049836947,\r
+                    -0.642787609686538697,-0.656059028990506499,\r
+                    -0.669130606358857238,-0.681998360062497477,\r
+                    -0.694658370458996033,-0.707106781186546240,\r
+                    -0.719339800338649749,-0.731353701619168906,\r
+                    -0.743144825477392579,-0.754709580222770238,\r
+                    -0.766044443118976237,-0.777145961456968903,\r
+                    -0.788010753606719905,-0.798635510047290720,\r
+                    -0.809016994374945231,-0.819152044288989578,\r
+                    -0.829037572555039404,-0.838670567945421719,\r
+                    -0.848048096156423625,-0.857167300702109891,\r
+                    -0.866025403784436265,-0.874619707139393410,\r
+                    -0.882947592858924435,-0.891006524188365345,\r
+                    -0.898794046299164484,-0.906307787036647494,\r
+                    -0.913545457642598424,-0.920504853452437932,\r
+                    -0.927183854566784982,-0.933580426497199412,\r
+                    -0.939692620785906096,-0.945518575599314515,\r
+                    -0.951056516295151311,-0.956304755963033326,\r
+                    -0.961261695938316785,-0.965925826289066314,\r
+                    -0.970295726275994586,-0.974370064785233358,\r
+                    -0.978147600733803912,-0.981627183447662310,\r
+                    -0.984807753012206577,-0.987688340595136327,\r
+                    -0.990268068741569030,-0.992546151641320873,\r
+                    -0.994521895368272291,-0.996194698091744657,\r
+                    -0.997564050259823532,-0.998629534754573389,\r
+                    -0.999390827019095318,-0.999847695156391048,\r
+                    -1.000000000000000000,-0.999847695156391381,\r
+                    -0.999390827019096095,-0.998629534754574499,\r
+                    -0.997564050259825086,-0.996194698091746544,\r
+                    -0.994521895368274622,-0.992546151641323537,\r
+                    -0.990268068741572027,-0.987688340595139658,\r
+                    -0.984807753012210241,-0.981627183447666418,\r
+                    -0.978147600733808353,-0.974370064785238244,\r
+                    -0.970295726275999804,-0.965925826289071865,\r
+                    -0.961261695938322669,-0.956304755963039654,\r
+                    -0.951056516295157972,-0.945518575599321509,\r
+                    -0.939692620785913424,-0.933580426497207072,\r
+                    -0.927183854566793086,-0.920504853452446370,\r
+                    -0.913545457642607195,-0.906307787036656598,\r
+                    -0.898794046299173921,-0.891006524188375226,\r
+                    -0.882947592858934649,-0.874619707139403846,\r
+                    -0.866025403784447034,-0.857167300702120993,\r
+                    -0.848048096156435061,-0.838670567945433487,\r
+                    -0.829037572555051505,-0.819152044289001902,\r
+                    -0.809016994374957998,-0.798635510047303709,\r
+                    -0.788010753606733227,-0.777145961456982559,\r
+                    -0.766044443118990004,-0.754709580222784449,\r
+                    -0.743144825477407012,-0.731353701619183672,\r
+                    -0.719339800338664737,-0.707106781186561450,\r
+                    -0.694658370459011576,-0.681998360062513242,\r
+                    -0.669130606358873337,-0.656059028990522708,\r
+                    -0.642787609686555128,-0.629320391049853711,\r
+                    -0.615661475325674945,-0.601815023152065254,\r
+                    -0.587785252292490457,-0.573576436351063812,\r
+                    -0.559192903470765002,-0.544639035015045625,\r
+                    -0.529919264233223886,-0.515038074910073473,\r
+                    -0.500000000000019651,-0.484809620246357043,\r
+                    -0.469471562785911123,-0.453990499739567510,\r
+                    -0.438371146789098498,-0.422618261740720869,\r
+                    -0.406736643075822024,-0.390731128489295920,\r
+                    -0.374606593415934497,-0.358367949545323083,\r
+                    -0.342020143325691917,-0.325568154457180181,\r
+                    -0.309016994374971266,-0.292371704722760861,\r
+                    -0.275637355817023644,-0.258819045102545497,\r
+                    -0.241921895599692793,-0.224951054343890372,\r
+                    -0.207911690817784989,-0.190808995376570756,\r
+                    -0.173648177666956560,-0.156434465040257376,\r
+                    -0.139173100960092194,-0.121869343405174496,\r
+                    -0.104528463267680741,-0.087155742747685686,\r
+                    -0.069756473744153044,-0.052335956242971805,\r
+                    -0.034899496702529162,-0.017452406437311916,\r
+                    -0.000000000000028605,0.017452406437254715,\r
+                    0.034899496702471985,0.052335956242914670,\r
+                    0.069756473744095979,0.087155742747628689,\r
+                    0.104528463267623842,0.121869343405117708,\r
+                    0.139173100960035545,0.156434465040200865,\r
+                    0.173648177666900216,0.190808995376514606,\r
+                    0.207911690817729033,0.224951054343834611,\r
+                    0.241921895599637282,0.258819045102490264,\r
+                    0.275637355816968632,0.292371704722706127,\r
+                    0.309016994374916809,0.325568154457126058,\r
+                    0.342020143325638126,0.358367949545269682,\r
+                    0.374606593415881484,0.390731128489243240,\r
+                    0.406736643075769733,0.422618261740669021,\r
+                    0.438371146789047095,0.453990499739516551,\r
+                    0.469471562785860608,0.484809620246306972,\r
+                    0.499999999999970080,0.515038074910024402,\r
+                    0.529919264233175369,0.544639035014997663,\r
+                    0.559192903470717484,0.573576436351016961,\r
+                    0.587785252292444271,0.601815023152019624,\r
+                    0.615661475325629759,0.629320391049809191,\r
+                    0.642787609686511385,0.656059028990479520,\r
+                    0.669130606358830815,0.681998360062471387,\r
+                    0.694658370458970387,0.707106781186521038,\r
+                    0.719339800338624991,0.731353701619144592,\r
+                    0.743144825477368709,0.754709580222746812,\r
+                    0.766044443118953255,0.777145961456946477,\r
+                    0.788010753606698033,0.798635510047269292,\r
+                    0.809016994374924359,0.819152044288969150,\r
+                    0.829037572555019531,0.838670567945402290,\r
+                    0.848048096156404752,0.857167300702091572,\r
+                    0.866025403784418391,0.874619707139376090,\r
+                    0.882947592858907782,0.891006524188349247,\r
+                    0.898794046299148941,0.906307787036632395,\r
+                    0.913545457642583991,0.920504853452423943,\r
+                    0.927183854566771659,0.933580426497186644,\r
+                    0.939692620785893884,0.945518575599302968,\r
+                    0.951056516295140320,0.956304755963022890,\r
+                    0.961261695938306904,0.965925826289056988,\r
+                    0.970295726275985926,0.974370064785225365,\r
+                    0.978147600733796474,0.981627183447655538,\r
+                    0.984807753012200360,0.987688340595130776,\r
+                    0.990268068741564034,0.992546151641316543,\r
+                    0.994521895368268627,0.996194698091741548,\r
+                    0.997564050259821089,0.998629534754571502,\r
+                    0.999390827019094097,0.999847695156390381\r
+     \r
+     int dtoi(double x)\r
+     {\r
+          /* Rounds a floating point number to an integer */\r
+     \r
+          int y;\r
+     \r
+          y = x;\r
+          if (y < (x + 0.5))\r
+               y++;\r
+          return(y);\r
+     }\r
+     \r
+     int getmode(int *ncols)\r
+     {\r
+          /* Returns current video mode and number of columns in ncols */\r
+     \r
+          union REGS inreg,outreg;\r
+     \r
+          inreg.h.ah = 0x0F;\r
+          int86(0x10, &inreg, &outreg);\r
+          *ncols = outreg.h.ah;\r
+          return(outreg.h.al);\r
+     }\r
+     \r
+     void setvideo(unsigned char mode)\r
+     {\r
+          /* Sets the video display mode and clears the screen */\r
+          /* (modes above 127 on VGA monitors do not clear the screen) */\r
+     \r
+          asm mov ax , mode;\r
+          asm int 10h;\r
+     \r
+          /* Set global variables */\r
+          switch(mode)\r
+          {\r
+               case 0:\r
+               case 1:\r
+               case 2:\r
+               case 3: break;\r
+               case 4:\r
+               case 9:\r
+               case 13:\r
+               case 19:\r
+               case 5: maxx = 320;\r
+                    maxy = 200;\r
+                    break;\r
+               case 14:\r
+               case 10:\r
+               case 6: maxx = 640;\r
+                    maxy = 200;\r
+                    break;\r
+               case 7: maxx = 720;\r
+                    maxy = 400;\r
+                    break;\r
+               case 8: maxx = 160;\r
+                    maxy = 200;\r
+                    break;\r
+               case 15:\r
+               case 16: maxx = 640;\r
+                     maxy = 350;\r
+                     break;\r
+               case 17:\r
+               case 18: maxx = 640;\r
+                     maxy = 480;\r
+                     break;\r
+     \r
+          }\r
+          _dmode = mode;\r
+     }\r
+     \r
+     void getcursor(int *row, int *col)\r
+     {\r
+          /* Returns the cursor position and size for the currently\r
+     active\r
+             video display page */\r
+     \r
+          union REGS inreg,outreg;\r
+     \r
+          inreg.h.bh = 0;\r
+          inreg.h.ah = 0x03;\r
+          int86(0x10, &inreg, &outreg);\r
+          *row = outreg.h.dh;\r
+          *col = outreg.h.dl;\r
+     }\r
+     \r
+     void setcursor(char status)\r
+     {\r
+          /* Set cursor size, if status is 0x20 the cursor is not\r
+     displayed */\r
+     \r
+          asm mov ah,1\r
+          asm mov ch,status\r
+          asm mov cl,7\r
+          asm int 10h\r
+     }\r
+     \r
+     void at(int row, int col)\r
+     {\r
+          /* Position text cursor on current screen */\r
+     \r
+          asm mov bh , 0;\r
+          asm mov dh , row;\r
+          asm mov dl , col;\r
+          asm mov ah , 02h;\r
+          asm int 10h;\r
+     }\r
+     \r
+     void clsc(unsigned char attrib)\r
+     {\r
+          /* Clear display and fill with new attribute */\r
+     \r
+          asm mov ax , 073dh;\r
+          asm mov bh , attrib;\r
+          asm mov cx , 0;\r
+          asm mov dx , 3c4fh;\r
+          asm int 10h;\r
+     \r
+          /* Now move text cursor to origin (0,0) */\r
+          asm mov bh , 0;\r
+          asm mov dx , 0;\r
+          asm mov ah , 02h;\r
+          asm int 10h;\r
+     }\r
+     \r
+     void clrwindow(int x1, int y1,int x2, int y2, unsigned char attrib)\r
+     {\r
+          /* Clear a text window */\r
+     \r
+          union REGS inreg,outreg;\r
+     \r
+          inreg.h.al = (unsigned char)(y2 - y1 + 1);\r
+          inreg.h.bh = attrib;\r
+          inreg.h.cl = (unsigned char)x1;\r
+          inreg.h.ch = (unsigned char)y1;\r
+          inreg.h.dl = (unsigned char)x2;\r
+          inreg.h.dh = (unsigned char)y2;\r
+          inreg.h.ah = 0x06;\r
+          int86(0x10, &inreg, &outreg);\r
+     }\r
+     \r
+     void scrollsc(void)\r
+     {\r
+          /* Scroll the text screen */\r
+     \r
+          asm mov al,01h;\r
+          asm mov cx,0\r
+          asm mov dx,3c4fh;\r
+          asm mov bh,attribute;\r
+          asm mov ah, 06h;\r
+          asm int 10h;\r
+     }\r
+     \r
+     void winscr(unsigned char left,unsigned char top, unsigned char\r
+     right,unsigned char bottom,\r
+               unsigned char direction, unsigned char numlines)\r
+     {\r
+          /* Scroll a text window */\r
+     \r
+          asm mov al , numlines;\r
+          asm mov cl , left;\r
+          asm mov ch , top;\r
+          asm mov dl , right;\r
+          asm mov dh , bottom;\r
+          asm mov bh , attribute;\r
+          asm mov ah , direction;\r
+          asm int 10h;\r
+     }\r
+     \r
+     unsigned char attr(int foregrnd, int backgrnd)\r
+     {\r
+          /* Convert a colour pair into a single attribute */\r
+     \r
+          return((char)((backgrnd << 4) + foregrnd));\r
+     }\r
+     \r
+     void shadewin(unsigned char left, unsigned char top, unsigned char\r
+     right,   unsigned char bottom)\r
+     {\r
+          /* Shade a text window */\r
+     \r
+          int row;\r
+          int col;\r
+          int oldrow;\r
+          int oldcol;\r
+     \r
+          /* Preserve existing coords */\r
+          getcursor(&oldrow,&oldcol);\r
+     \r
+          col = right + 1;\r
+          for (row = top + 1; row <= bottom + 1; row++)\r
+          {\r
+               /* Move to row,col */\r
+               asm mov bh , 0;\r
+               asm mov dh , row;\r
+               asm mov dl , col;\r
+               asm mov ah , 02h;\r
+               asm int 10h;\r
+               /* Get character */\r
+               asm mov ah , 0Fh;\r
+               asm int 10h;\r
+               asm mov ah , 08h;\r
+               asm int 10h;\r
+     \r
+               /* Write in attribute 7 */\r
+               asm mov ah , 09h;\r
+               asm mov bl, 07h;\r
+               asm mov cx, 01h;\r
+               asm int 10h;\r
+          }\r
+          bottom++;\r
+          for (col = left + 1; col <= right + 1; col++)\r
+          {\r
+               /* Move to row,col */\r
+               asm mov bh , 0;\r
+               asm mov dh , bottom;\r
+               asm mov dl , col;\r
+               asm mov ah , 02h;\r
+               asm int 10h;\r
+     \r
+               /* Get character */\r
+               asm mov ah , 0Fh;\r
+               asm int 10h;\r
+               asm mov ah , 08h;\r
+               asm int 10h;\r
+     \r
+               /* Write in attribute 7 */\r
+               asm mov ah , 09h;\r
+               asm mov bl, 07h;\r
+               asm mov cx, 01h;\r
+               asm int 10h;\r
+          }\r
+     \r
+          /* Return to original position */\r
+          /* Move to row,col */\r
+          asm mov bh , 0;\r
+          asm mov dh , oldrow;\r
+          asm mov dl , oldcol;\r
+          asm mov ah , 02h;\r
+          asm int 10h;\r
+     }\r
+     \r
+     void bprintf(char *format, ...)\r
+     {\r
+          /* print text to graphics screen correctly */\r
+     \r
+          va_list arg_ptr;\r
+          char output[1000];\r
+          int c, r, n, p = 0;\r
+          unsigned char text;\r
+          unsigned char page;\r
+     \r
+          va_start(arg_ptr, format);\r
+          vsprintf(output, format, arg_ptr);\r
+     \r
+          if (strcmp(output,"\r\n") == 0)\r
+               fputs(output,stdout);\r
+     \r
+          else\r
+          {\r
+               asm mov ah , 0Fh;\r
+               asm int 10h;\r
+               asm mov page, bh;\r
+     \r
+               getmode(&n);\r
+               getcursor(&r,&c);\r
+     \r
+               while (output[p])\r
+               {\r
+                    text = output[p++];\r
+                    asm mov bh , page;\r
+                    asm mov bl , attribute;\r
+                    asm mov cx , 01h;\r
+                    asm mov ah , 09h;\r
+                    asm mov al , text;\r
+                    asm int 10h;\r
+     \r
+                    c++;\r
+                    if (c < (n-1))\r
+                         at( r, c);\r
+                    else\r
+                    {\r
+                         c = 0;\r
+                         at(++r,0);\r
+                    }\r
+               }\r
+          }\r
+     }\r
+     \r
+     void wrtstr(char *output)\r
+     {\r
+          /* TTY text output. The original screen attributes remain\r
+     unaffected */\r
+     \r
+          int p = 0;\r
+          unsigned char page;\r
+          unsigned char text;\r
+     \r
+          asm mov ah , 0Fh;\r
+          asm int 10h;\r
+          asm mov page, bh;\r
+     \r
+          while (output[p])\r
+          {\r
+               text = output[p++];\r
+               asm mov bh , page;\r
+               asm mov ah , 0Eh;\r
+               asm mov al , text;\r
+               asm int 10h;\r
+          }\r
+     }\r
+     \r
+     void getwin(int left, int top, int right, int bottom, char *buffer)\r
+     {\r
+          /* Read a text window into a variable */\r
+     \r
+          int oldleft;\r
+          unsigned char page;\r
+     \r
+          asm mov ah , 0Fh;\r
+          asm int 10h;\r
+          asm mov page, bh;\r
+     \r
+          while(top <= bottom)\r
+          {\r
+               oldleft = left;\r
+               while(left <= right)\r
+               {\r
+                    at(top,left);\r
+                    asm mov bh , page;\r
+                    asm mov ah , 08h;\r
+                    asm int 10h;\r
+                    *buffer++ = _AL;\r
+                    *buffer++ = _AH;\r
+                    left++;\r
+               }\r
+               left = oldleft;\r
+               top++;\r
+          }\r
+     }\r
+     \r
+     void putwin(int left, int top, int right, int bottom,char *buffer)\r
+     {\r
+          /* Display a text window from a variable */\r
+     \r
+          int oldleft;\r
+          unsigned char chr;\r
+          unsigned char attr;\r
+          unsigned char page;\r
+     \r
+          asm mov ah , 0Fh;\r
+          asm int 10h;\r
+          asm mov page, bh;\r
+     \r
+          while(top <= bottom)\r
+          {\r
+               oldleft = left;\r
+               while(left <= right)\r
+               {\r
+                    at(top,left);\r
+                    chr = *buffer++;\r
+                    attr = *buffer++;\r
+                    asm mov bh , page;\r
+                    asm mov ah , 09h;\r
+                    asm mov al, chr;\r
+                    asm mov bl, attr;\r
+                    asm mov cx,1;\r
+                    asm int 10h;\r
+                    left++;\r
+               }\r
+               left = oldleft;\r
+               top++;\r
+          }\r
+     }\r
+     \r
+     void setpalette(unsigned char palno)\r
+     {\r
+          /* Sets the video palette */\r
+     \r
+          asm mov bh,01h;\r
+          asm mov bl,palno;\r
+          asm mov ah,0Bh;\r
+          asm int 10h;\r
+     }\r
+     \r
+     void setborder(unsigned char x)\r
+     {\r
+          /* Set border colour */\r
+     \r
+          asm mov bh, x;\r
+          asm mov ax ,1001h;\r
+          asm int 10h;\r
+     }\r
+     \r
+     void setlines(unsigned char x)\r
+     {\r
+          /* Set text display number of lines */\r
+     \r
+          asm mov ah,11h;\r
+          asm mov al,x;\r
+          asm mov bl,0;\r
+          asm int 10h;\r
+     }\r
+     \r
+     void graphbackground(unsigned char colour)\r
+     {\r
+          /* Selects the background colour for a graphics mode */\r
+     \r
+          asm mov bh,0;\r
+          asm mov bl,colour;\r
+          asm mov ah, 0Bh;\r
+          asm int 10h;\r
+     }\r
+     \r
+     void plot(int x, int y, unsigned char colour)\r
+     {\r
+          /* Sets a pixel at the specified coordinates to the specified\r
+     colour.\r
+             The variables _lastx and _lasty are updated. */\r
+     \r
+          unsigned char far *video;\r
+     \r
+          _lastx = x;\r
+          _lasty = y;\r
+     \r
+          if (_dmode == 19)\r
+          {\r
+               video = MK_FP(0xA000,0);\r
+               video[x+y*320] = colour;\r
+          }\r
+          else\r
+          {\r
+               asm mov al , colour;\r
+               asm mov bh , 00;\r
+               asm mov cx , x;\r
+               asm mov dx , y;\r
+               asm mov ah , 0Ch;\r
+               asm int 10h;\r
+          }\r
+     }\r
+     \r
+     int pixset(int x, int y)\r
+     {\r
+          /* Returns the colour of the specified pixel */\r
+     \r
+          asm mov cx ,x;\r
+          asm mov dx ,y;\r
+          asm mov ah ,0Dh;\r
+          asm int 10h;\r
+          return(_AL);\r
+     }\r
+     \r
+     void move(int x, int y)\r
+     {\r
+          /* Sets the value of the variables _lastx and _lasty */\r
+     \r
+          _lastx = x;\r
+          _lasty = y;\r
+     }\r
+     \r
+     void line(int a, int b, int c, int d, int col)\r
+     {\r
+          /* Draws a straight line from (a,b) to (c,d) in colour col */\r
+     \r
+          int u;\r
+          int v;\r
+          int d1x;\r
+          int d1y;\r
+          int d2x;\r
+          int d2y;\r
+          int m;\r
+          int n;\r
+          int s;\r
+          int i;\r
+     \r
+          if (a < 0)\r
+               a = 0;\r
+          else\r
+          if (a > maxx)\r
+               a = maxx;\r
+     \r
+          if (c < 0)\r
+               c = 0;\r
+          else\r
+          if (c > maxx)\r
+               c = maxx;\r
+     \r
+          if (b < 0)\r
+               b = 0;\r
+          else\r
+          if (b > maxy)\r
+               b = maxy;\r
+     \r
+          if (d < 0)\r
+               d = 0;\r
+          else\r
+          if (d > maxy)\r
+               d = maxy;\r
+     \r
+          u = c - a;\r
+          v = d - b;\r
+     \r
+          if (u == 0)\r
+          {\r
+               d1x = 0;\r
+               m = 0;\r
+          }\r
+          else\r
+          {\r
+               m = abs(u);\r
+               if (u < 0)\r
+                    d1x = -1;\r
+               else\r
+               if (u > 0)\r
+                    d1x = 1;\r
+          }\r
+          if ( v == 0)\r
+          {\r
+               d1y = 0;\r
+               n = 0;\r
+          }\r
+          else\r
+          {\r
+               n = abs(v);\r
+               if (v < 0)\r
+                    d1y = -1;\r
+               else\r
+               if (v > 0)\r
+                    d1y = 1;\r
+          }\r
+          if (m > n)\r
+          {\r
+               d2x = d1x;\r
+               d2y = 0;\r
+          }\r
+          else\r
+          {\r
+               d2x = 0;\r
+               d2y = d1y;\r
+               m = n;\r
+               n = abs(u);\r
+          }\r
+          s = m / 2;\r
+     \r
+          for (i = 0; i <= m; i++)\r
+          {\r
+               asm mov al , col;\r
+               asm mov bh , 0;\r
+               asm mov ah ,0Ch;\r
+               asm mov cx ,a;\r
+               asm mov dx ,b;\r
+               asm int 10h;\r
+     \r
+               s += n;\r
+               if (s >= m)\r
+               {\r
+                    s -= m;\r
+                    a += d1x;\r
+                    b += d1y;\r
+               }\r
+               else\r
+               {\r
+                    a += d2x;\r
+                    b += d2y;\r
+               }\r
+          }\r
+          _lastx = a;\r
+          _lasty = b;\r
+     }\r
+     \r
+     void ellipse(int x, int y, int xrad, int yrad,double incline,int\r
+     col)\r
+     {\r
+          /* Draws an ellipse */\r
+     \r
+          int f;\r
+          float a;\r
+          float b;\r
+          float c;\r
+          float d;\r
+          int cols;\r
+          double div;\r
+     \r
+          incline = 1 / sintable[(int)incline];\r
+     \r
+          if (getmode(&cols) == 6)\r
+               div = 2.2;\r
+          else\r
+               div = 1.3;\r
+     \r
+               /* Compensate for pixel shape */\r
+     \r
+          a = x + xrad;\r
+          b = y + sintable[0] * yrad + xrad/incline / div;\r
+     \r
+          for(f = 5; f < 360; f += 5)\r
+          {\r
+               c = x + costable[f] * xrad;\r
+               d = y + sintable[f] * yrad + (costable[f] *\r
+     xrad)/incline/div;\r
+     \r
+               line(a,b,c,d,col);\r
+     \r
+               a = c;\r
+               b = d;\r
+          }\r
+          /* Ensure join */\r
+          line(a,b,x + xrad,y + sintable[0] * yrad + xrad/incline /\r
+     div,col);\r
+     }\r
+     \r
+     void polygon(int x, int y, int rad, int col, int sides, int start)\r
+     {\r
+          /* Draws a regular polygon */\r
+     \r
+          double f;\r
+          double div;\r
+          double a;\r
+          double b;\r
+          double c;\r
+          double d;\r
+          double aa;\r
+          double bb;\r
+          int cols;\r
+          double step;\r
+     \r
+          step = 360 / sides;\r
+     \r
+          if (getmode(&cols) == 6)\r
+               div = 2.2;\r
+          else\r
+               div = 1.3;\r
+          aa = a = x + costable[start] * rad;\r
+          bb = b = y + sintable[start] * rad / div;\r
+     \r
+          for(f = start + step; f < start + 360; f += step)\r
+          {\r
+               c = x + costable[(int)f] * rad;\r
+               d = y + sintable[(int)f] * rad / div;\r
+               line(a,b,c,d,col);\r
+               a = c;\r
+               b = d;\r
+          }\r
+          line(a,b,aa,bb,col);\r
+     }\r
+     \r
+     void arc(int x, int y, int rad, int start, int end,int col)\r
+     {\r
+          /* Draw an arc */\r
+     \r
+          int f;\r
+          float a;\r
+          float b;\r
+          float c;\r
+          float d;\r
+          int cols;\r
+          float div;\r
+     \r
+          if (getmode(&cols) == 6)\r
+               div = 2.2;\r
+          else\r
+               div = 1.3;\r
+          a = x + costable[start] * rad;\r
+          b = y + sintable[start] * rad / div;\r
+     \r
+          for(f = start; f <= end; f ++)\r
+          {\r
+               c = x + costable[f] * rad;\r
+               d = y + sintable[f] * rad / div;\r
+               line(a,b,c,d,col);\r
+               a = c;\r
+               b = d;\r
+          }\r
+     }\r
+     \r
+     void segm(int x, int y, int rad, int start, int end,int col)\r
+     {\r
+          /* Draw a segment of a circle */\r
+     \r
+          int f;\r
+          float a;\r
+          float b;\r
+          float c;\r
+          float d;\r
+          int cols;\r
+          double div;\r
+     \r
+          if (getmode(&cols) == 6)\r
+               div = 2.2;\r
+          else\r
+               div = 1.3;\r
+          a = x + costable[start] * rad;\r
+          b = y + sintable[start] * rad / div;\r
+     \r
+          line(x,y,a,b,col);\r
+     \r
+          for(f = start; f <= end ; f ++)\r
+          {\r
+               c = x + costable[f] * rad;\r
+               d = y + sintable[f] * rad / div;\r
+               line(a,b,c,d,col);\r
+               a = c;\r
+               b = d;\r
+          }\r
+          line(x,y,a,b,col);\r
+     }\r
+     \r
+     void box(int xa,int ya, int xb, int yb, int col)\r
+     {\r
+          /* Draws a box for use in 2d histogram graphs etc */\r
+     \r
+          line(xa,ya,xa,yb,col);\r
+          line(xa,yb,xb,yb,col);\r
+          line(xb,yb,xb,ya,col);\r
+          line(xb,ya,xa,ya,col);\r
+     }\r
+     \r
+     void tri(int xa,int ya, int xb, int yb, int xc, int yc,int col)\r
+     {\r
+          /* Draw a triangle */\r
+     \r
+          line(xa,ya,xb,yb,col);\r
+          line(xb,yb,xc,yc,col);\r
+          line(xc,yc,xa,ya,col);\r
+     }\r
+     \r
+     void fill(int x, int y, int col,int pattern)\r
+     {\r
+          /* Fill a boundered shape using a hatch pattern */\r
+     \r
+          int xa;\r
+          int ya;\r
+          int bn;\r
+          int byn;\r
+     \r
+          int hatch[10][8] = {     255,255,255,255,255,255,255,255,\r
+                         128,64,32,16,8,4,2,1,\r
+                         1,2,4,8,16,32,64,128,\r
+                         1,2,4,8,8,4,2,1,\r
+                         238,238,238,238,238,238,238,238,\r
+                         170,85,170,85,170,85,170,85,\r
+                         192,96,48,24,12,6,3,1,\r
+                         62,62,62,0,227,227,227,0,\r
+                         129,66,36,24,24,36,66,129,\r
+                         146,36,146,36,146,36,146,36\r
+                    };\r
+     \r
+          /* Patterns for fill, each integer describes a row of dots */\r
+     \r
+          xa = x;\r
+          ya = y;  /* Save Origin */\r
+     \r
+          if(pixset(x,y))\r
+               return;\r
+     \r
+          bn = 1;\r
+          byn = 0;\r
+     \r
+          do\r
+          {\r
+               if (hatch[pattern][byn] != 0)\r
+               {\r
+                    /* If blank ignore */\r
+                    do\r
+                    {\r
+                         if ((bn & hatch[pattern][byn]) == bn)\r
+                         {\r
+                              asm mov al , col;\r
+                              asm mov bh , 00;\r
+                              asm mov cx , x;\r
+                              asm mov dx , y;\r
+                              asm mov ah , 0Ch;\r
+                              asm int 10h;\r
+                         }\r
+                         x--;\r
+                         bn <<= 1;\r
+                         if (bn > 128)\r
+                              bn = 1;\r
+                    }\r
+                    while(!pixset(x,y) && (x > -1));\r
+     \r
+                    x = xa + 1;\r
+                    bn = 128;\r
+     \r
+                    do\r
+                    {\r
+                         if ((bn & hatch[pattern][byn]) == bn)\r
+                         {\r
+                              asm mov al , col;\r
+                              asm mov bh , 00;\r
+                              asm mov cx , x;\r
+                              asm mov dx , y;\r
+                              asm mov ah , 0Ch;\r
+                              asm int 10h;\r
+                         }\r
+                         x++;\r
+                         bn >>=1;\r
+                         if (bn <1)\r
+                              bn = 128;\r
+                    }\r
+                    while((!pixset(x,y)) && (x <= maxx));\r
+               }\r
+               x = xa;\r
+               y--;\r
+               bn = 1;\r
+               byn++;\r
+               if (byn > 7)\r
+                    byn = 0;\r
+     \r
+          }\r
+          while(!pixset(x,y) && ( y > -1));\r
+     \r
+          /* Now travel downwards */\r
+     \r
+          y = ya + 1;\r
+     \r
+          byn = 7;\r
+          bn = 1;\r
+          do\r
+          {\r
+               /* Travel left */\r
+               if (hatch[pattern][byn] !=0)\r
+               {\r
+                    do\r
+                    {\r
+                         if ( (bn & hatch[pattern][byn]) == bn)\r
+                         {\r
+                              asm mov al , col;\r
+                              asm mov bh , 00;\r
+                              asm mov cx , x;\r
+                              asm mov dx , y;\r
+                              asm mov ah , 0Ch;\r
+                              asm int 10h;\r
+                         }\r
+     \r
+                         x--;\r
+                         bn <<= 1;\r
+                         if (bn > 128)\r
+                              bn = 1;\r
+                    }\r
+                    while(!pixset(x,y) && (x > -1));\r
+     \r
+                    /* Back to x origin */\r
+                    x = xa + 1 ;\r
+                    bn = 128;\r
+     \r
+                    /* Travel right */\r
+                    do\r
+                    {\r
+                         if ((bn & hatch[pattern][byn]) == bn)\r
+                         {\r
+                              asm mov al , col;\r
+                              asm mov bh , 00;\r
+                              asm mov cx , x;\r
+                              asm mov dx , y;\r
+                              asm mov ah , 0Ch;\r
+                              asm int 10h;\r
+                         }\r
+                         x++;\r
+                         bn >>=1;\r
+                         if (bn <1)\r
+                              bn = 128;\r
+                    }\r
+                    while((!pixset(x,y)) && (x <= maxx));\r
+               }\r
+               x = xa;\r
+               bn = 1;\r
+               y++;\r
+               byn--;\r
+               if (byn < 0)\r
+                    byn = 7;\r
+          }\r
+          while((!pixset(x,y)) && (y <= maxy));\r
+     }\r
+     \r
+     void invert(int xa,int ya, int xb, int yb, int col)\r
+     {\r
+          /* Invert a pixel window */\r
+     \r
+          union REGS inreg,outreg;\r
+     \r
+          inreg.h.al = (unsigned char)(128 | col);\r
+          inreg.h.ah = 0x0C;\r
+          for(inreg.x.cx = (unsigned int)xa;      inreg.x.cx <= (unsigned\r
+     int)xb; inreg.x.cx++)\r
+               for(inreg.x.dx = (unsigned)ya; inreg.x.dx <= (unsigned)yb;\r
+     inreg.x.dx++)\r
+                    int86(0x10, &inreg, &outreg);\r
+     }\r
+     \r
+     void circle(int x_centre , int y_centre, int radius, int colour)\r
+     {\r
+          int x,y,delta;\r
+          int startx,endx,x1,starty,endy,y1;\r
+          int asp_ratio;\r
+     \r
+          if (_dmode == 6)\r
+               asp_ratio = 22;\r
+          else\r
+               asp_ratio = 13;\r
+     \r
+          y = radius;\r
+          delta = 3 - 2 * radius;\r
+          for(x = 0; x < y; )\r
+          {\r
+               starty = y * asp_ratio / 10;\r
+               endy = (y + 1) * asp_ratio / 10;\r
+               startx = x * asp_ratio / 10;\r
+               endx = (x + 1) * asp_ratio / 10;\r
+               for(x1 = startx; x1 < endx; ++x1)\r
+               {\r
+                    plot(x1+x_centre,y+y_centre,colour);\r
+                    plot(x1+x_centre,y_centre - y,colour);\r
+                    plot(x_centre - x1,y_centre - y,colour);\r
+                    plot(x_centre - x1,y + y_centre,colour);\r
+               }\r
+     \r
+               for(y1 = starty; y1 < endy; ++y1)\r
+               {\r
+                    plot(y1+x_centre,x+y_centre,colour);\r
+                    plot(y1+x_centre,y_centre - x,colour);\r
+                    plot(x_centre - y1,y_centre - x,colour);\r
+                    plot(x_centre - y1,x + y_centre,colour);\r
+               }\r
+     \r
+               if (delta < 0)\r
+                    delta += 4 * x + 6;\r
+               else\r
+               {\r
+                    delta += 4*(x-y)+10;\r
+                    y--;\r
+               }\r
+               x++;\r
+          }\r
+     \r
+          if(y)\r
+          {\r
+               starty = y * asp_ratio / 10;\r
+               endy = (y + 1) * asp_ratio / 10;\r
+               startx = x * asp_ratio / 10;\r
+               endx = (x + 1) * asp_ratio / 10;\r
+               for(x1 = startx; x1 < endx; ++x1)\r
+               {\r
+                    plot(x1+x_centre,y+y_centre,colour);\r
+                    plot(x1+x_centre,y_centre - y,colour);\r
+                    plot(x_centre - x1,y_centre - y,colour);\r
+                    plot(x_centre - x1,y + y_centre,colour);\r
+               }\r
+     \r
+               for(y1 = starty; y1 < endy; ++y1)\r
+               {\r
+                    plot(y1+x_centre,x+y_centre,colour);\r
+                    plot(y1+x_centre,y_centre - x,colour);\r
+                    plot(x_centre - y1,y_centre - x,colour);\r
+                    plot(x_centre - y1,x + y_centre,colour);\r
+               }\r
+          }\r
+     }\r
+     \r
+     void draw(int x, int y, int colour)\r
+     {\r
+          /* Draws a line from _lastx,_lasty to x,y */\r
+     \r
+          line(_lastx,_lasty,x,y,colour);\r
+     }\r
+     \r
+     void psprite(SPRITE *sprite,int x,int y)\r
+     {\r
+          int origx;\r
+          int origy;\r
+          int z;\r
+          int count;\r
+          int col;\r
+          int pos;\r
+          unsigned char far *video;\r
+     \r
+          if (_dmode == 19)\r
+          {\r
+               /* Super fast direct video write in mode 19 for sprites */\r
+     \r
+               video = MK_FP(0xA000,0);\r
+     \r
+               origx = x;\r
+               origy = y;\r
+     \r
+               if (sprite->x != -1)\r
+               {\r
+                    /* This sprite has been displayed before */\r
+                    /* replace background */\r
+                    /* This must be done first in case the sprite\r
+     overlaps itself */\r
+                    x = sprite->x;\r
+                    y = sprite->y;\r
+                    col = 0;\r
+                    pos = x + y * 320;\r
+                    for(count = 0; count < 256; count++)\r
+                    {\r
+                         video[pos] = sprite->save[count];\r
+                         col++;\r
+                         if (col == 16)\r
+                         {\r
+                              pos += 305;\r
+                              col = 0;\r
+                         }\r
+                         else\r
+                              pos++;\r
+                    }\r
+               }\r
+     \r
+               x = origx;\r
+               y = origy;\r
+               col = 0;\r
+     \r
+               pos = x + y * 320;\r
+     \r
+               for(count = 0; count < 256; count++)\r
+               {\r
+                    sprite->save[count] = video[pos];\r
+                    z = sprite->data[count];\r
+                    if (z != 255)\r
+                         video[pos] = z;\r
+     \r
+                    col++;\r
+                    if (col == 16)\r
+                    {\r
+                         pos += 305;\r
+                         col = 0;\r
+                    }\r
+                    else\r
+                         pos++;\r
+               }\r
+               sprite->x = origx;\r
+               sprite->y = origy;\r
+     \r
+               return;\r
+          }\r
+     \r
+          origx = x;\r
+          origy = y;\r
+     \r
+          if (sprite->x != -1)\r
+          {\r
+               /* This sprite has been displayed before */\r
+               /* replace background */\r
+               /* This must be done first in case the sprite overlaps\r
+     itself */\r
+               x = sprite->x;\r
+               y = sprite->y;\r
+               col = 0;\r
+               for(count = 0; count < 256; count++)\r
+               {\r
+                    if ((x >= 0) && (y >= 0) && (x < maxx) && (y < maxy))\r
+                    {\r
+                         z = sprite->save[count];\r
+                         asm mov al , z;\r
+                         asm mov bh , 00;\r
+                         asm mov cx , x;\r
+                         asm mov dx , y;\r
+                         asm mov ah , 0Ch;\r
+                         asm int 10h;\r
+                    }\r
+                    col++;\r
+                    if (col == 16)\r
+                    {\r
+                         y++;\r
+                         x = sprite->x;\r
+                         col = 0;\r
+                    }\r
+                    else\r
+                         x++;\r
+               }\r
+          }\r
+     \r
+          x = origx;\r
+          y = origy;\r
+          col = 0;\r
+     \r
+          for(count = 0; count < 256; count++)\r
+          {\r
+               if ((x >= 0) && (y >= 0) && (x < maxx) && (y < maxy))\r
+               {\r
+                    asm mov cx , x;\r
+                    asm mov dx , y;\r
+                    asm mov ah , 0Dh;\r
+                    asm int 10h;\r
+                    asm mov z ,al;\r
+                    sprite->save[count] = z;\r
+                    z = sprite->data[count];\r
+     \r
+                    if (z != 255)\r
+                    {\r
+                         asm mov al , z;\r
+                         asm mov bh , 0;\r
+                         asm mov cx , x;\r
+                         asm mov dx , y;\r
+                         asm mov ah , 0Ch;\r
+                         asm int 10h;\r
+                    }\r
+               }\r
+               col++;\r
+               if (col == 16)\r
+               {\r
+                    y++;\r
+                    x = origx;\r
+                    col = 0;\r
+               }\r
+               else\r
+                    x++;\r
+          }\r
+          sprite->x = origx;\r
+          sprite->y = origy;\r
+          return;\r
+     }\r
+     \r
+     \r
+\r
+Displaying A PCX File\r
+\r
+The following program is offered as a practical example of graphics with\r
+the IBM PC. It reads a file of the `PCX' format and displays the image on\r
+the screen.\r
+\r
+     \r
+     /* Read a PCX file and display image */\r
+     \r
+     #include <dos.h>\r
+     #include <io.h>\r
+     #include <fcntl.h>\r
+     \r
+     typedef struct\r
+     {\r
+          unsigned char man;\r
+          unsigned char version;\r
+          unsigned char encoding;\r
+          unsigned char bpp;\r
+          int xmin;\r
+          int ymin;\r
+          int xmax;\r
+          int ymax;\r
+          int hdpi;\r
+          int vdpi;\r
+          int colormap[24];\r
+          char reserved;\r
+          unsigned char planes;\r
+          int bpl;\r
+          int palette;\r
+          int hss;\r
+          int vsize;\r
+          char pad[54];\r
+     }\r
+     PCX_HEADER;\r
+     \r
+     PCX_HEADER header;\r
+     \r
+     int x;\r
+     int y;\r
+     \r
+     union REGS inreg,outreg;\r
+     \r
+     void setvideo(unsigned char mode)\r
+     {\r
+          /* Sets the video display mode     and clears the screen */\r
+     \r
+          inreg.h.al = mode;\r
+          inreg.h.ah = 0x00;\r
+          int86(0x10, &inreg, &outreg);\r
+     }\r
+     \r
+     void plot(int x, int y, unsigned char colour)\r
+     {\r
+     \r
+             if (x < header.xmax && y < header.ymax)\r
+             {\r
+     \r
+             /* Direct video plot in modes 16 & 18 only! */\r
+             asm mov   ax,y;\r
+             asm mov   dx,80;\r
+             asm mul   dx;\r
+             asm mov   bx,x;\r
+             asm mov   cl,bl;\r
+     \r
+             asm shr   bx,1;\r
+             asm shr   bx,1;\r
+             asm shr   bx,1;\r
+             asm add   bx,ax;\r
+     \r
+             asm and   cl,7;\r
+             asm xor   cl,7;\r
+             asm mov   ah,1;\r
+             asm shl   ah,cl;\r
+     \r
+             asm mov   dx,3ceh;\r
+             asm mov   al,8;\r
+             asm out   dx,ax;\r
+     \r
+             asm mov   ax,(02h shl 8) + 5;\r
+             asm out   dx,ax;\r
+     \r
+             asm mov   ax,0A000h;\r
+             asm mov   es,ax;\r
+     \r
+             asm mov   al,es:[bx];\r
+             asm mov   al,byte ptr colour;\r
+             asm mov   es:[bx],al;\r
+     \r
+             asm mov   ax,(0FFh shl 8 ) + 8;\r
+             asm out   dx,ax;\r
+     \r
+             asm mov   ax,(00h shl 8) + 5;\r
+             asm out   dx,ax;\r
+          }\r
+     }\r
+     \r
+     void DISPLAY(unsigned char data)\r
+     {\r
+          int n;\r
+          int bit;\r
+     \r
+          bit = 32;\r
+     \r
+          for (n = 0; n < 6; n++)\r
+          {\r
+               if (data & bit)\r
+                    plot(x,y,1);\r
+               else\r
+                    plot(x,y,0);\r
+               bit >>= 1;\r
+               x++;\r
+          }\r
+     }\r
+     \r
+     main(int argc, char *argv[])\r
+     {\r
+          int fp;\r
+          int total_bytes;\r
+          int n;\r
+          unsigned char data;\r
+          int count;\r
+          int scan;\r
+     \r
+          if (argc != 2)\r
+          {\r
+               puts("USAGE IS getpcx <filename>");\r
+               exit(0);\r
+          }\r
+     \r
+          setvideo(16);\r
+     \r
+          x = 0;\r
+          y = 0;\r
+     \r
+          fp = open(argv[1],O_RDONLY|O_BINARY);\r
+     \r
+          _read(fp,&header,128);\r
+     \r
+          total_bytes = header.planes * header.bpl;\r
+     \r
+          for(scan = 0; scan <= header.ymax; scan++)\r
+          {\r
+               x = 0;\r
+     \r
+               /* First scan line */\r
+     \r
+               for(n = 0; n < total_bytes; n++)\r
+               {\r
+                    /* Read byte */\r
+                    _read(fp,&data,1);\r
+     \r
+                    count = data & 192;\r
+     \r
+                    if (count == 192)\r
+                    {\r
+                         count = data & 63;\r
+                         n += count - 1;\r
+                         _read(fp,&data,1);\r
+                         while(count)\r
+                         {\r
+                              DISPLAY(data);\r
+                              count--;\r
+                         }\r
+                    }\r
+                    else\r
+                         DISPLAY(data);\r
+     \r
+               }\r
+               x = 0;\r
+               y++;\r
+          }\r
+     }\r
+          \r
+\r
+Drawing Circles\r
+\r
+\r
+What has drawing circles got to do with advanced C programming? Well\r
+quite a lot, it is a task which is often desired by modern programmers,\r
+and it is a task which can be attacked from a number of angles. This\r
+example illustrates some of the ideas already discussed for replacing\r
+floating point numbers with integers, and using lookup tables rather than\r
+repeat calls to maths functions.\r
+\r
+A circle may be drawn by plotting each point on its circumference. The\r
+location of any point is given by;\r
+\r
+\r
+     Xp = Xo + Sine(Angle) * Radius\r
+     Yp = Yo + Cosine(Angle) * Radius\r
+\r
+Where Xp,Yp is the point to be plotted, and Xo,Yo is the centre of the\r
+circle.\r
+\r
+Thus, the simplest way to draw a circle is to calculate Xp and Yp for\r
+each angle between 1 and 360 degrees and to plot these points. There is\r
+however one fundamental error with this. As the radius of the circle\r
+increases, so also does the length of the arc between each angular point.\r
+Thus a circle of sufficient radius will be drawn with a dotted line\r
+rather than a solid line.\r
+\r
+The problem of the distance between the angular points may be tackled in\r
+two ways;\r
+\r
+  1)The number of points to be plotted can be increased, to say every 30\r
+     minutes.\r
+  2)A straight line may be drawn between each angular point.\r
+\r
+The simplest circle drawing pseudo-code may then look like this;\r
+\r
+\r
+     FOR angle = 1 TO 360\r
+          PLOT Xo + SINE(angle) * radius, Yo + COSINE(angle) * radius\r
+     NEXT angle\r
+\r
+This code has two major disadvantages;\r
+\r
+     1) It uses REAL numbers for the sine and cosine figures\r
+     2) It makes numerous calculations of sine and cosine values\r
+\r
+Both of these disadvantages result in a very slow piece of code. Since a\r
+circle is a regular figure with two axis of symmetry, one in both the X\r
+and Y axis, one only needs to calculate the relative offsets of points in\r
+one quadrant of the circle and then these offsets may be applied to the\r
+other three quadrants to produce a faster piece of code. Faster because\r
+the slow sine and cosine calculations are only done 90 times instead of\r
+360 times;\r
+\r
+\r
+     FOR angle = 1 TO 90\r
+          Xa = SINE(angle) * radius\r
+          Ya = COSINE(angle) * radius\r
+          PLOT Xo + Xa, Yo + Ya\r
+          PLOT Xo + Xa, Yo - Ya\r
+          PLOT Xo - Xa, Yo + Ya\r
+          PLOT Xo - Xa, Yo - Ya\r
+     NEXT angle\r
+\r
+A further enhancement may be made by making use of sine and cosine lookup\r
+tables instead of calculating them. This means calculating the sine and\r
+cosine values for each required angle and storing them in a table. Then,\r
+instead of calculating the values for each angle the circle drawing code\r
+need only retrieve the values from a table;\r
+\r
+\r
+     FOR angle = 1 TO 90\r
+          Xa = SINE[angle] * radius\r
+          Ya = COSINE[angle] * radius\r
+          PLOT Xo + Xa, Yo + Ya\r
+          PLOT Xo + Xa, Yo - Ya\r
+          PLOT Xo - Xa, Yo + Ya\r
+          PLOT Xo - Xa, Yo - Ya\r
+     NEXT angle\r
+\r
+\r
+Most computer languages work in RADIANS rather than DEGREES. There being\r
+approximately 57 degrees in one radian, 2 * PI radians in one circle.\r
+This implies that to calculate sine and cosine values of sufficient\r
+points to draw a reasonable circle using radians one must again use real\r
+numbers, that is numbers which have figures following a decimal point.\r
+Real number arithmetic, also known as floating point arithmetic, is\r
+horrendously slow to calculate. Integer arithmetic on the other hand is\r
+very quick to calculate, but less precise.\r
+\r
+To use integer arithmetic in circle drawing code requires ingenuity. If\r
+one agrees to use sine and cosine lookup tables for degrees, rather than\r
+radians. Then the sine value of an angle of 1 degree is;\r
+\r
+       0.0175\r
+\r
+Which, truncated to an integer becomes zero! To overcome this the sine\r
+and cosine values held in the table should be multiplied by some factor,\r
+say 10000. Then, the integer value of the sine of an angle of 1 degree\r
+becomes;\r
+\r
+       175\r
+\r
+If the sine and cosine values have been multiplied by a factor, then when\r
+the calculation of the point's offset is carried out one must remember to\r
+divide the result by the same factor. Thus the calculation becomes;\r
+\r
+          Xa = SINE[angle] * radius / factor\r
+          Ya = COSINE[angle] * radius / factor\r
+\r
+The final obstacle to drawing circles on a computer is the relationship\r
+between the width of the display screen and its height. This ratio\r
+between width and height is known as the "aspect ratio" and varies upon\r
+video display mode. The IBM VGA 256 colour mode for example can display\r
+320 pixels across and 200 up the screen. This equates to an aspect ratio\r
+of 1:1.3. If the circle drawing code ignores the aspect ratio, then the\r
+shape displayed will often be ovalar to a greater or lesser degree due to\r
+the rectangular shape of the display pixels. Thus in order to display a\r
+true circle, the formulae to calculate each point on the circumference\r
+must be amended to calculate a slight ellipse in compensation of the\r
+distorting factor of the display.\r
+\r
+The offset formulae then become;\r
+\r
+          Xa = SINE[angle] * radius / factor\r
+          Ya = COSINE[angle] * radius / (factor * aspect ratio)\r
+\r
+The following short C program illustrates a practical circle drawing code\r
+segment, in a demonstrable  form;\r
+\r
+\r
+     /* Circles.c   A demonstration circle drawing program for the IBM PC\r
+     */\r
+     \r
+     \r
+     #include <stdlib.h>\r
+     \r
+     int sintable[91] = {0,175,349,523,698,\r
+               872,1045,1219,1392,\r
+               1564,1736,1908,2079,\r
+               2250,2419,2588,2756,\r
+               2924,3090,3256,3420,\r
+               3584,3746,3907,4067,\r
+               4226,4384,4540,4695,\r
+               4848,5000,5150,5299,\r
+               5446,5592,5736,5878,\r
+               6018,6157,6293,6428,\r
+               6561,6691,6820,6947,\r
+               7071,7193,7314,7431,\r
+               7547,7660,7771,7880,\r
+               7986,8090,8192,8290,\r
+               8387,8480,8572,8660,\r
+               8746,8829,8910,8988,\r
+               9063,9135,9205,9272,\r
+               9336,9397,9455,9511,\r
+               9563,9613,9659,9703,\r
+               9744,9781,9816,9848,\r
+               9877,9903,9925,9945,\r
+               9962,9976,9986,9994,\r
+               9998,10000\r
+     };\r
+     \r
+     int costable[91] = { 10000,9998,9994,9986,9976,\r
+               9962,9945,9925,9903,\r
+               9877,9848,9816,9781,\r
+               9744,9703,9659,9613,\r
+               9563,9511,9455,9397,\r
+               9336,9272,9205,9135,\r
+               9063,8988,8910,8829,\r
+               8746,8660,8572,8480,\r
+               8387,8290,8192,8090,\r
+               7986,7880,7771,7660,\r
+               7547,7431,7314,7193,\r
+               7071,6947,6820,6691,\r
+               6561,6428,6293,6157,\r
+               6018,5878,5736,5592,\r
+               5446,5299,5150,5000,\r
+               4848,4695,4540,4384,\r
+               4226,4067,3907,3746,\r
+               3584,3420,3256,3090,\r
+               2924,2756,2588,2419,\r
+               2250,2079,1908,1736,\r
+               1564,1392,1219,1045,\r
+               872,698,523,349,\r
+               175,0\r
+     };\r
+     \r
+     void setvideo(unsigned char mode)\r
+     {\r
+          /* Sets the video display mode for an IBM PC */\r
+     \r
+          asm mov al , mode;\r
+          asm mov ah , 00;\r
+          asm int 10h;\r
+     }\r
+     \r
+     void plot(int x, int y, unsigned char colour)\r
+     {\r
+          /* Code for IBM PC BIOS ROM */\r
+          /* Sets a pixel at the specified coordinates to a specified\r
+     colour */\r
+     \r
+          /* Return if out of range */\r
+          if (x < 0 || y < 0 || x > 320 || y > 200)\r
+               return;\r
+     \r
+          asm mov al , colour;\r
+          asm mov bh , 0;\r
+          asm mov cx , x;\r
+          asm mov dx , y;\r
+          asm mov ah, 0Ch;\r
+          asm int 10h;\r
+     }\r
+     \r
+     void Line(int a, int b, int c, int d, int col)\r
+     {\r
+          /* Draws a straight line from point a,b to point c,d in colour\r
+     col */\r
+     \r
+          int u;\r
+          int v;\r
+          int d1x;\r
+          int d1y;\r
+          int d2x;\r
+          int d2y;\r
+          int m;\r
+          int n;\r
+          double s; /* The only real number variable, but it's essential\r
+     */\r
+          int i;\r
+     \r
+          u = c - a;\r
+          v = d - b;\r
+          if (u == 0)\r
+          {\r
+               d1x = 0;\r
+               m = 0;\r
+          }\r
+          else\r
+          {\r
+               m = abs(u);\r
+               if (u < 0)\r
+                    d1x = -1;\r
+               else\r
+               if (u > 0)\r
+                    d1x = 1;\r
+          }\r
+     \r
+          if ( v == 0)\r
+          {\r
+               d1y = 0;\r
+               n = 0;\r
+          }\r
+          else\r
+          {\r
+               n = abs(v);\r
+               if (v < 0)\r
+                    d1y = -1;\r
+               else\r
+               if (v > 0)\r
+                    d1y = 1;\r
+          }\r
+          if (m > n)\r
+          {\r
+               d2x = d1x;\r
+               d2y = 0;\r
+          }\r
+          else\r
+          {\r
+               d2x = 0;\r
+               d2y = d1y;\r
+               m = n;\r
+               n = abs(u);\r
+          }\r
+          s = m / 2;\r
+     \r
+          for (i = 0; i <= m; i++)\r
+          {\r
+               plot(a,b,col);\r
+               s += n;\r
+               if (s >= m)\r
+               {\r
+                    s -= m;\r
+                    a += d1x;\r
+                    b += d1y;\r
+               }\r
+               else\r
+               {\r
+                    a += d2x;\r
+                    b += d2y;\r
+               }\r
+          }\r
+     }\r
+     \r
+     \r
+     void Circle(int x, int y, int rad, int col)\r
+     {\r
+          /* Draws a circle about origin x,y */\r
+          /* With a radius of rad */\r
+          /* The col parameter defines the colour for plotting */\r
+     \r
+          int f;\r
+          long xa;\r
+          long ya;\r
+          int a1;\r
+          int b1;\r
+          int a2;\r
+          int b2;\r
+          int a3;\r
+          int b3;\r
+          int a4;\r
+          int b4;\r
+     \r
+     \r
+          /* Calculate first point in each segment */\r
+     \r
+          a1 = x + ((long)(costable[0]) * (long)(rad) + 5000) / 10000;\r
+          b1 = y + ((long)(sintable[0]) * (long)(rad) + 5000) / 13000;\r
+     \r
+          a2 = x - ((long)(costable[0]) * (long)(rad) + 5000) / 10000;\r
+          b2 = y + ((long)(sintable[0]) * (long)(rad) + 5000) / 13000;\r
+     \r
+          a3 = x - ((long)(costable[0]) * (long)(rad) + 5000) / 10000;\r
+          b3 = y - ((long)(sintable[0]) * (long)(rad) + 5000) / 13000;\r
+     \r
+          a4 = x + ((long)(costable[0]) * (long)(rad) + 5000) / 10000;\r
+          b4 = y - ((long)(sintable[0]) * (long)(rad) + 5000) / 13000;\r
+     \r
+          /* Start loop at second point */\r
+          for(f = 1; f <= 90; f++)\r
+          {\r
+               /* Calculate offset to new point */\r
+               xa = ((long)(costable[f]) * (long)(rad) + 5000) / 10000;\r
+               ya = ((long)(sintable[f]) * (long)(rad) + 5000) / 13000;\r
+     \r
+               /* Draw a line from the previous point to the new point in\r
+                  each segment */\r
+               Line(a1,b1,x + xa, y + ya,col);\r
+               Line(a2,b2,x - xa, y + ya,col);\r
+               Line(a3,b3,x - xa, y - ya,col);\r
+               Line(a4,b4,x + xa, y - ya,col);\r
+     \r
+               /* Update the previous point in each segment */\r
+               a1 = x + xa;\r
+               b1 = y + ya;\r
+               a2 = x - xa;\r
+               b2 = y + ya;\r
+               a3 = x - xa;\r
+               b3 = y - ya;\r
+               a4 = x + xa;\r
+               b4 = y - ya;\r
+          }\r
+     }\r
+     \r
+     main()\r
+     {\r
+          int n;\r
+     \r
+          /* Select VGA 256 colour 320 x 200 video mode */\r
+          setvideo(19);\r
+     \r
+          /* Draw some circles */\r
+          for(n = 0; n < 100; n++)\r
+               Circle(160,100,n,n + 20);\r
+     }\r
+     \r
+     \r
+\r
+Vesa Mode\r
+\r
+The VESA BIOS provides a number of new, and exciting video modes not\r
+supported by the standard BIOS. These modes vary from one video card to\r
+another, but most support the following modes:\r
+\r
+Mode               Display\r
+                   \r
+0x54               Text 16 colours 132 x 43\r
+0x55               Text 16 colours 132 x 25\r
+0x58               Graphics 16 colours 800 x 600\r
+0x5C               Graphics 256 colours 800 x 600\r
+0x5D               Graphics 16 colours 1024 x 768\r
+0x5F               Graphics 256 colours 640 x 480\r
+0x60               Graphics 256 colours 1024 x 768\r
+0x64               Graphics 64k colours 640 x 480\r
+0x65               Graphics 64k colours 800 x 600\r
+0x6A               Graphics 16 colours 800 x 600\r
+0x6C               Graphics 16 colours 1280 x 1024\r
+0x70               Graphics 16m colours 320 x 200\r
+0x71               Graphics 16m colours 640 x 480\r
+\r
+These modes are in addition to the standard BIOS video modes described\r
+earlier.\r
+\r
+Setting a VESA video mode requires a call to a different BIOS function\r
+than the standard BIOS, as illustrated in the following example which\r
+enables any VESA or standard display mode to be selected from the DOS\r
+command line.\r
+\r
+     #include <dos.h>\r
+     #include <ctype.h>\r
+     \r
+     void setvideo(int mode)\r
+     {\r
+          /* Sets the video display to a VESA or normal mode and clears\r
+     the screen */\r
+     \r
+          union REGS inreg,outreg;\r
+     \r
+          inreg.h.ah = 0x4f;\r
+          inreg.h.al = 0x02;\r
+          inreg.x.bx = mode;\r
+          int86(0x10, &inreg, &outreg);\r
+     }\r
+     \r
+     main(int argc, char *argv[])\r
+     {\r
+          setvideo(atoi(argv[1]));\r
+     }\r
+\r
+\r
+Plotting pixels in a VESA mode graphics display can be acgieved with the\r
+standard BIOS plot functiona call, as illustrated here;\r
+\r
+     \r
+     void plot(int x, int y, unsigned char colour)\r
+     {\r
+          asm mov al , colour;\r
+          asm mov bh , 00;\r
+          asm mov cx , x;\r
+          asm mov dx , y;\r
+          asm mov ah , 0Ch;\r
+          asm int 10h;\r
+     }\r
+\r
+\r
+Or, in a 800 x 600 resolution mode you can use this fast direct video\r
+access plot function;\r
+\r
+     void plot( int x, int y, unsigned char pcolor)\r
+     {\r
+          /*\r
+               Fast 800 x 600 mode (0x58 or 0x102) plotting\r
+          */\r
+     \r
+          asm mov   ax,y;\r
+          asm mov   dx,800/8;\r
+          asm mul   dx;\r
+          asm mov   bx,x;\r
+          asm mov   cl,bl;\r
+     \r
+          asm shr   bx,1;\r
+          asm shr   bx,1;\r
+          asm shr   bx,1;\r
+          asm add   bx,ax;\r
+     \r
+          asm and   cl,7;\r
+          asm xor   cl,7;\r
+          asm mov   ah,1;\r
+          asm shl   ah,cl;\r
+     \r
+          asm mov   dx,03CEh;\r
+          asm mov   al,8;\r
+          asm out   dx,ax;\r
+     \r
+          asm mov   ax,(02h shl 8) + 5;\r
+          asm out   dx,ax;\r
+     \r
+          asm mov   ax,0A000h;\r
+          asm mov   es,ax;\r
+     \r
+          asm mov   al,es:[bx];\r
+          asm mov   al,byte ptr pcolor;\r
+          asm mov   es:[bx],al;\r
+     \r
+          asm mov   ax,(0FFh shl 8 ) + 8;\r
+          asm out   dx,ax;\r
+     \r
+          asm mov   ax,(00h shl 8) + 5;\r
+          asm out   dx,ax;\r
+     }\r
+\r
+There are lots more functions supported by the VESA BIOS, but this will\r
+get you going with the basic operations. Remember though that when using\r
+VESA display modes, that the direct console I/O functions in the C\r
+compiler library may not function correctly.\r
+                                    \r
+               DIRECTORY SEARCHING WITH THE IBM PC AND DOS\r
+\r
+Amongst the many functions provided by DOS for programmers, are a pair of\r
+functions; "Find first" and "Find next" which are used to search a DOS\r
+directory for a specified file name or names. The first function, "Find\r
+first" is accessed via DOS interrupt 21, function 4E. It takes an ascii\r
+string file specification, which can include wildcards, and the required\r
+attribute for files to match. Upon return the function fills the disk\r
+transfer area (DTA) with details of the located file and returns with the\r
+carry flag clear. If an error occurs, such as no matching files are\r
+located, the function returns with the carry flag set.\r
+\r
+Following a successful call to "Find first", a program can call "Find\r
+next", DOS interrupt 21, function 4F, to locate the next file matching\r
+the specifications provided by the initial call to "Find first". If this\r
+function succeeds, then the DTA is filled in with details of the next\r
+matching file, and the function returns with the carry flag clear.\r
+Otherwise a return is made with the carry flag set.\r
+\r
+Most C compilers for the IBM PC provide non standard library functions\r
+for accessing these two functions. Turbo C provides "findfirst()" and\r
+"findnext()". Making use of the supplied library functions shields the\r
+programmer from the messy task of worrying about the DTA. Microsoft C\r
+programmers should substitue findfirst() with _dos_findfirst() and\r
+findnext() with _dos_findnext().\r
+\r
+The following Turbo C example imitates the DOS directory command, in a\r
+basic form;\r
+\r
+     #include <stdio.h>\r
+     #include <dir.h>\r
+     #include <dos.h>\r
+     \r
+     void main(void)\r
+     {\r
+          /* Display directory listing of current directory */\r
+     \r
+          int done;\r
+          int day;\r
+          int month;\r
+          int year;\r
+          int hour;\r
+          int min;\r
+          char amflag;\r
+          struct ffblk ffblk;\r
+          struct fcb fcb;\r
+     \r
+          /* First display sub directory entries */\r
+          done = findfirst("*.",&ffblk,16);\r
+     \r
+          while (!done)\r
+          {\r
+               year = (ffblk.ff_fdate >> 9) + 80;\r
+               month = (ffblk.ff_fdate >> 5) & 0x0f;\r
+               day = ffblk.ff_fdate & 0x1f;\r
+               hour = (ffblk.ff_ftime >> 11);\r
+               min = (ffblk.ff_ftime >> 5) & 63;\r
+     \r
+               amflag = 'a';\r
+     \r
+               if (hour > 12)\r
+               {\r
+                    hour -= 12;\r
+                    amflag = 'p';\r
+               }\r
+     \r
+               printf("%-11.11s  <DIR>   %02d-%02d-%02d  %2d:%02d%c\n",\r
+                       ffblk.ff_name,day,month,year,hour,min,amflag);\r
+               done = findnext(&ffblk);\r
+          }\r
+     \r
+          /* Now all files except directories */\r
+          done = findfirst("*.*",&ffblk,231);\r
+     \r
+          while (!done)\r
+          {\r
+               year = (ffblk.ff_fdate >> 9) + 80;\r
+               month = (ffblk.ff_fdate >> 5) & 0x0f;\r
+               day = ffblk.ff_fdate & 0x1f;\r
+               hour = (ffblk.ff_ftime >> 11);\r
+               min = (ffblk.ff_ftime >> 5) & 63;\r
+     \r
+               amflag = 'a';\r
+     \r
+               if (hour > 12)\r
+               {\r
+                    hour -= 12;\r
+                    amflag = 'p';\r
+               }\r
+     \r
+               parsfnm(ffblk.ff_name,&fcb,1);\r
+     \r
+               printf("%-8.8s %-3.3s %8ld  %02d-%02d-%02d  %2d:%02d%c\n",\r
+                         fcb.fcb_name,fcb.fcb_ext,ffblk.ff_fsize,\r
+                         day,month,year,hour,min,amflag);\r
+               done = findnext(&ffblk);\r
+          }\r
+     }\r
+     \r
+\r
+The function "parsfnm()" is a Turbo C library command which makes use of\r
+the DOS function for parsing an ascii string containing a file name, into\r
+its component parts. These component parts are then put into a DOS file\r
+control block (fcb), from where they may be easily retrieved for\r
+displaying by printf().\r
+\r
+\r
+The DOS DTA is comprised as follows;\r
+\r
+Offset                   Length                   Contents\r
+                                                  \r
+00                       15                       Reserved\r
+15                       Byte                     Attribute of matched\r
+                                                  file\r
+16                       Word                     File time\r
+18                       Word                     File date\r
+1A                       04                       File size\r
+1E                       0D                       File name and extension\r
+                                                  as ascii string\r
+\r
+The file time word contains the time at which the file was last written\r
+to disc and is comprised as follows;\r
+\r
+Bits      Contents\r
+\r
+ 0 -  4             Seconds divided by 2\r
+ 5 - 10        Minutes\r
+11 - 15   Hours\r
+\r
+The file date word holds the date on which the file was last written to\r
+disc and is comprised of;\r
+\r
+Bits      Contents\r
+\r
+ 0 -  4             Day\r
+ 5 -  8             Month\r
+ 9 - 15        Years since 1980\r
+\r
+To extract these details from the DTA requires a little manipulation, as\r
+illustrated in the above example.\r
+\r
+The DTA attribute flag is comprised of the following bits being set or\r
+not;\r
+\r
+Bit       Attribute\r
+\r
+0         Read only\r
+1         Hidden\r
+2         System\r
+3         Volume label\r
+4         Directory\r
+5         Archive\r
+                                    \r
+                        ACCESSING EXPANDED MEMORY\r
+\r
+Memory (RAM) in an IBM PC comes in three flavours; Conventional, Expanded\r
+and Extended. Conventional memory is the 640K of RAM which the operating\r
+system DOS can access. This memory is normally used. However, it is often\r
+insufficient for todays RAM hungry systems. Expanded memory is RAM which\r
+is addressed outside of the area of conventional RAM not by DOS but by a\r
+second program called a LIM EMS driver. Access to this device driver is\r
+made through interrupt 67h.\r
+\r
+The main problem with accessing expanded memory is that no matter how\r
+much expanded memory is fitted to the computer, it can only be accessed\r
+through 16K blocks refered to as pages. So for example. If you have 2mB\r
+of expanded RAM fitted to your PC then that is comprised of 128 pages\r
+(128 * 16K = 2mB).\r
+\r
+A program can determine whether a LIM EMS driver is installed by\r
+attempting to open the file `EMMXXXX0' which is guarranteed by the LIM\r
+standard to be present as an IOCTL device when the device driver is\r
+active.\r
+\r
+The following source code illustrates some basic functions for testing\r
+for and accessing expanded memory.\r
+\r
+     /*\r
+     Various functions for using Expanded memory\r
+     */\r
+     \r
+     #include <dos.h>\r
+     #define   EMM  0x67\r
+     \r
+     char far *emmbase;\r
+     emmtest()\r
+     {\r
+          /*\r
+          Tests for the presence of expnaded memory by attempting to\r
+          open the file EMMXXXX0.\r
+          */\r
+     \r
+          union REGS regs;\r
+          struct SREGS sregs;\r
+          int error;\r
+          long handle;\r
+     \r
+          /* Attempt to open the file device EMMXXXX0 */\r
+          regs.x.ax = 0x3d00;\r
+          regs.x.dx = (int)"EMMXXXX0";\r
+          sregs.ds = _DS;\r
+          intdosx(&regs,&regs,&sregs);\r
+          handle = regs.x.ax;\r
+          error = regs.x.cflag;\r
+     \r
+          if (!error)\r
+          {\r
+               regs.h.ah = 0x3e;\r
+               regs.x.bx = handle;\r
+               intdos(&regs,&regs);\r
+          }\r
+          return error;\r
+     }\r
+     \r
+     emmok()\r
+     {\r
+          /*\r
+          Checks whether the expanded memory manager responds correctly\r
+          */\r
+     \r
+          union REGS regs;\r
+     \r
+          regs.h.ah = 0x40;\r
+          int86(EMM,&regs,&regs);\r
+     \r
+          if (regs.h.ah)\r
+               return 0;\r
+     \r
+          regs.h.ah = 0x41;\r
+          int86(EMM,&regs,&regs);\r
+     \r
+          if (regs.h.ah)\r
+               return 0;\r
+     \r
+          emmbase = MK_FP(regs.x.bx,0);\r
+          return 1;\r
+     }\r
+     \r
+     long emmavail()\r
+     {\r
+        /*\r
+        Returns the number of available (free) 16K pages of expanded\r
+     memory\r
+        or -1 if an error occurs.\r
+        */\r
+     \r
+             union REGS regs;\r
+     \r
+          regs.h.ah = 0x42;\r
+          int86(EMM,&regs,&regs);\r
+          if (!regs.h.ah)\r
+               return regs.x.bx;\r
+          return -1;\r
+     }\r
+     \r
+     long emmalloc(int n)\r
+     {\r
+          /*\r
+          Requests 'n' pages of expanded memory and returns the file\r
+     handle\r
+          assigned to the pages or -1 if there is an error\r
+          */\r
+     \r
+          union REGS regs;\r
+     \r
+          regs.h.ah = 0x43;\r
+          regs.x.bx = n;\r
+          int86(EMM,&regs,&regs);\r
+          if (regs.h.ah)\r
+               return -1;\r
+          return regs.x.dx;\r
+     }\r
+     \r
+     emmmap(long handle, int phys, int page)\r
+     {\r
+          /*\r
+          Maps a physical page from expanded memory into the page frame\r
+     in the\r
+          conventional memory 16K window so that data can be transfered\r
+     between\r
+          the expanded memory and conventional memory.\r
+          */\r
+     \r
+          union REGS regs;\r
+     \r
+          regs.h.ah = 0x44;\r
+          regs.h.al = page;\r
+          regs.x.bx = phys;\r
+          regs.x.dx = handle;\r
+          int86(EMM,&regs,&regs);\r
+          return (regs.h.ah == 0);\r
+     }\r
+     \r
+     void emmmove(int page, char *str, int n)\r
+     {\r
+          /*\r
+          Move 'n' bytes from conventional memory to the specified\r
+     expanded memory\r
+          page\r
+          */\r
+     \r
+          char far *ptr;\r
+     \r
+          ptr = emmbase + page * 16384;\r
+          while(n-- > 0)\r
+               *ptr++ = *str++;\r
+     }\r
+     \r
+     void emmget(int page, char *str, int n)\r
+     {\r
+          /*\r
+          Move 'n' bytes from the specified expanded memory page into\r
+     conventional\r
+          memory\r
+          */\r
+     \r
+          char far *ptr;\r
+     \r
+          ptr = emmbase + page * 16384;\r
+          while(n-- > 0)\r
+               *str++ = *ptr++;\r
+     }\r
+     \r
+     emmclose(long handle)\r
+     {\r
+          /*\r
+          Release control of the expanded memory pages allocated to\r
+     'handle'\r
+          */\r
+     \r
+          union REGS regs;\r
+     \r
+          regs.h.ah = 0x45;\r
+          regs.x.dx = handle;\r
+          int86(EMM,&regs,&regs);\r
+          return (regs.h.ah == 0);\r
+     }\r
+     \r
+     /*\r
+     Test function for the EMM routines\r
+     */\r
+     \r
+     void main()\r
+     {\r
+          long emmhandle;\r
+          long avail;\r
+          char teststr[80];\r
+          int i;\r
+     \r
+          if(!emmtest())\r
+          {\r
+               printf("Expanded memory is not present\n");\r
+               exit(0);\r
+          }\r
+     \r
+          if(!emmok())\r
+          {\r
+               printf("Expanded memory manager is not present\n");\r
+               exit(0);\r
+          }\r
+     \r
+          avail = emmavail();\r
+          if (avail == -1)\r
+          {\r
+               printf("Expanded memory manager error\n");\r
+               exit(0);\r
+          }\r
+          printf("There are %ld pages available\n",avail);\r
+     \r
+          /* Request 10 pages of expanded memory */\r
+          if((emmhandle = emmalloc(10)) < 0)\r
+          {\r
+               printf("Insufficient pages available\n");\r
+               exit(0);\r
+          }\r
+     \r
+          for (i = 0; i < 10; i++)\r
+          {\r
+               sprintf(teststr,"%02d This is a test string\n",i);\r
+               emmmap(emmhandle,i,0);\r
+               emmmove(0,teststr,strlen(teststr) + 1);\r
+          }\r
+     \r
+          for (i = 0; i < 10; i++)\r
+          {\r
+               emmmap(emmhandle,i,0);\r
+               emmget(0,teststr,strlen(teststr) + 1);\r
+               printf("READING BLOCK %d: %s\n",i,teststr);\r
+          }\r
+     \r
+          emmclose(emmhandle);\r
+     }\r
+                                    \r
+                        ACCESSING EXTENDED MEMORY\r
+\r
+\r
+Extended memory has all but taken over from Expanded Memory now (1996).\r
+It is faster and more useable than expanded memory. As with Expanded\r
+memory, Extended memory cannot be directly accessed through the standard\r
+DOS mode, and so a transfer buffer in conventional or "real-mode" memory\r
+needs to be used. The process to write data to Extended memory then\r
+involves copying the data to the transfer buffer in conventional memory,\r
+and from there copying it to Extended memory.\r
+\r
+Before any use may be made of Extended memory, a program should test to\r
+see if Extended memory is available. The following function, XMS_init(),\r
+tests for the presence of Extended memory, and if available calls another\r
+function, GetXMSEntry()  to initialise the program for using Extended\r
+Memory. The function also allocates a conventional memory transfer\r
+buffer.\r
+\r
+\r
+     \r
+     /*\r
+          BLOCKSIZE will be the size of our real-memory buffer that\r
+          we'll swap XMS through (must be a multiple of 1024, since\r
+          XMS is allocated in 1K chunks.)\r
+     */\r
+     \r
+     #ifdef __SMALL__\r
+     #define BLOCKSIZE (16L * 1024L)\r
+     #endif\r
+     \r
+     \r
+     #ifdef __MEDIUM__\r
+     #define BLOCKSIZE (16L * 1024L)\r
+     #endif\r
+     \r
+     \r
+     #ifdef __COMPACT__\r
+     #define BLOCKSIZE (64L * 1024L)\r
+     #endif\r
+     \r
+     #ifdef __LARGE__\r
+     #define BLOCKSIZE (64L * 1024L)\r
+     #endif\r
+     \r
+     \r
+     char XMS_init()\r
+     {\r
+          /*\r
+               returns 0 if XMS present,\r
+                    1 if XMS absent\r
+                    2 if unable to allocate conventional memory transfer\r
+     buffer\r
+          */\r
+          unsigned char status;\r
+          _AX=0x4300;\r
+          geninterrupt(0x2F);\r
+          status = _AL;\r
+          if(status==0x80)\r
+          {\r
+               GetXMSEntry();\r
+               XMSBuf = (char far *) farmalloc(BLOCKSIZE);\r
+               if (XMSBuf == NULL)\r
+                    return 2;\r
+               return 0;\r
+          }\r
+          return 1;\r
+     }\r
+     \r
+     void GetXMSEntry(void)\r
+     {\r
+          /*\r
+               GetXMSEntry sets XMSFunc to the XMS Manager entry point\r
+               so we can call it later\r
+          */\r
+     \r
+          _AX=0x4310;\r
+          geninterrupt(0x2F);\r
+          XMSFunc= (void (far *)(void)) MK_FP(_ES,_BX);\r
+     }\r
+\r
+\r
+Once the presence of Extended memory has been confirmed, a program can\r
+find out how much Extended memory is available;\r
+\r
+     void XMSSize(int *kbAvail, int *largestAvail)\r
+     {\r
+          /*\r
+               XMSSize returns the total kilobytes available, and the\r
+     size\r
+               in kilobytes of the largest available block\r
+          */\r
+     \r
+          _AH=8;\r
+          (*XMSFunc)();\r
+          *largestAvail=_DX;\r
+          *kbAvail=_AX;\r
+     }\r
+\r
+\r
+The following function may be called to allocate a block of Extended\r
+memory, like you would allocate a block of conventional memory.\r
+\r
+     char AllocXMS(unsigned long numberBytes)\r
+     {\r
+          /*\r
+               Allocate a block of XMS memory numberBytes long\r
+               Returns 1 on success\r
+                    0 on failure\r
+          */\r
+     \r
+          _DX = (int)(numberBytes / 1024);\r
+          _AH = 9;\r
+          (*XMSFunc)();\r
+          if (_AX==0)\r
+          {\r
+               return 0;\r
+          }\r
+          XMSHandle=_DX;\r
+          return 1;\r
+     }\r
+     \r
+\r
+Allocated Extended memory is not freed by DOS. A program using Extended\r
+memory must release it before terminating. This function frees a block of\r
+extended memory previously allocated by AllocXMS. Note, XMSHandle is a\r
+global variable of type int.\r
+\r
+     void XMS_free(void)\r
+     {\r
+          /*\r
+               Free used XMS\r
+          */\r
+          _DX=XMSHandle;\r
+          _AH=0x0A;\r
+          (*XMSFunc)();\r
+     }\r
+\r
+\r
+Two functions are now given. One for writing data to Extended memory, and\r
+one for reading data from Extended memory into conventional memory.\r
+\r
+     /*\r
+          XMSParms is a structure for copying information to and from\r
+          real-mode memory to XMS memory\r
+     */\r
+     \r
+     struct parmstruct\r
+     {\r
+          /*\r
+               blocklength is the size in bytes of block to copy\r
+          */\r
+          unsigned long blockLength;\r
+     \r
+          /*\r
+               sourceHandle is the XMS handle of source; 0 means that\r
+               sourcePtr will be a 16:16 real-mode pointer, otherwise\r
+               sourcePtr is a 32-bit offset from the beginning of the\r
+               XMS area that sourceHandle points to\r
+          */\r
+     \r
+          unsigned int sourceHandle;\r
+          far void *sourcePtr;\r
+     \r
+          /*\r
+               destHandle is the XMS handle of destination; 0 means that\r
+               destPtr will be a 16:16 real-mode pointer, otherwise\r
+               destPtr is a 32-bit offset from the beginning of the XMS\r
+               area that destHandle points to\r
+          */\r
+     \r
+          unsigned int destHandle;\r
+          far void *destPtr;\r
+     }\r
+     XMSParms;\r
+     \r
+     \r
+     char XMS_write(unsigned long loc, char far *val, unsigned length)\r
+     {\r
+          /*\r
+               Round length up to next even value\r
+          */\r
+          length += length % 2;\r
+     \r
+          XMSParms.sourceHandle=0;\r
+          XMSParms.sourcePtr=val;\r
+          XMSParms.destHandle=XMSHandle;\r
+          XMSParms.destPtr=(void far *) (loc);\r
+          XMSParms.blockLength=length;  /* Must be an even number! */\r
+          _SI = FP_OFF(&XMSParms);\r
+          _AH=0x0B;\r
+          (*XMSFunc)();\r
+          if (_AX==0)\r
+          {\r
+               return 0;\r
+          }\r
+          return 1;\r
+     }\r
+     \r
+     \r
+     void *XMS_read(unsigned long loc,unsigned length)\r
+     {\r
+          /*\r
+               Returns pointer to data\r
+               or NULL on error\r
+          */\r
+     \r
+          /*\r
+               Round length up to next even value\r
+          */\r
+          length += length % 2;\r
+     \r
+          XMSParms.sourceHandle=XMSHandle;\r
+          XMSParms.sourcePtr=(void far *) (loc);\r
+          XMSParms.destHandle=0;\r
+          XMSParms.destPtr=XMSBuf;\r
+          XMSParms.blockLength=length;       /* Must be an even number */\r
+          _SI=FP_OFF(&XMSParms);\r
+          _AH=0x0B;\r
+          (*XMSFunc)();\r
+          if (_AX==0)\r
+          {\r
+               return NULL;\r
+          }\r
+          return XMSBuf;\r
+     }\r
+     \r
+     \r
+And now putting it all together is a demonstration program.\r
+\r
+     /* A sequential table of variable length records in XMS */\r
+     \r
+     #include <dos.h>\r
+     #include <stdio.h>\r
+     #include <stdlib.h>\r
+     #include <alloc.h>\r
+     #include <string.h>\r
+     \r
+     #define TRUE 1\r
+     #define FALSE 0\r
+     \r
+     /*\r
+          BLOCKSIZE will be the size of our real-memory buffer that\r
+          we'll swap XMS through (must be a multiple of 1024, since\r
+          XMS is allocated in 1K chunks.)\r
+     */\r
+     \r
+     #ifdef __SMALL__\r
+     #define BLOCKSIZE (16L * 1024L)\r
+     #endif\r
+     \r
+     \r
+     #ifdef __MEDIUM__\r
+     #define BLOCKSIZE (16L * 1024L)\r
+     #endif\r
+     \r
+     \r
+     #ifdef __COMPACT__\r
+     #define BLOCKSIZE (64L * 1024L)\r
+     #endif\r
+     \r
+     #ifdef __LARGE__\r
+     #define BLOCKSIZE (64L * 1024L)\r
+     #endif\r
+     \r
+     \r
+     /*\r
+          XMSParms is a structure for copying information to and from\r
+          real-mode memory to XMS memory\r
+     */\r
+     \r
+     struct parmstruct\r
+     {\r
+          /*\r
+               blocklength is the size in bytes of block to copy\r
+          */\r
+          unsigned long blockLength;\r
+     \r
+          /*\r
+               sourceHandle is the XMS handle of source; 0 means that\r
+               sourcePtr will be a 16:16 real-mode pointer, otherwise\r
+               sourcePtr is a 32-bit offset from the beginning of the\r
+               XMS area that sourceHandle points to\r
+          */\r
+     \r
+          unsigned int sourceHandle;\r
+          far void *sourcePtr;\r
+     \r
+          /*\r
+               destHandle is the XMS handle of destination; 0 means that\r
+               destPtr will be a 16:16 real-mode pointer, otherwise\r
+               destPtr is a 32-bit offset from the beginning of the XMS\r
+               area that destHandle points to\r
+          */\r
+     \r
+          unsigned int destHandle;\r
+          far void *destPtr;\r
+     }\r
+     XMSParms;\r
+     \r
+     void far (*XMSFunc) (void);   /* Used to call XMS manager\r
+     (himem.sys) */\r
+     char GetBuf(void);\r
+     void GetXMSEntry(void);\r
+     \r
+     char *XMSBuf;  /* Conventional memory buffer for transfers */\r
+     \r
+     unsigned int XMSHandle;  /* handle to allocated XMS block */\r
+     \r
+     \r
+     char XMS_init()\r
+     {\r
+          /*\r
+               returns 0 if XMS present,\r
+                    1 if XMS absent\r
+                    2 if unable to allocate transfer buffer\r
+          */\r
+          unsigned char status;\r
+          _AX=0x4300;\r
+          geninterrupt(0x2F);\r
+          status = _AL;\r
+          if(status==0x80)\r
+          {\r
+               GetXMSEntry();\r
+               XMSBuf = (char far *) farmalloc(BLOCKSIZE);\r
+               if (XMSBuf == NULL)\r
+                    return 2;\r
+               return 0;\r
+          }\r
+          return 1;\r
+     }\r
+     \r
+     void GetXMSEntry(void)\r
+     {\r
+          /*\r
+               GetXMSEntry sets XMSFunc to the XMS Manager entry point\r
+               so we can call it later\r
+          */\r
+     \r
+          _AX=0x4310;\r
+          geninterrupt(0x2F);\r
+          XMSFunc= (void (far *)(void)) MK_FP(_ES,_BX);\r
+     }\r
+     \r
+     \r
+     void XMSSize(int *kbAvail, int *largestAvail)\r
+     {\r
+          /*\r
+               XMSSize returns the total kilobytes available, and the\r
+     size\r
+               in kilobytes of the largest available block\r
+          */\r
+     \r
+          _AH=8;\r
+          (*XMSFunc)();\r
+          *largestAvail=_DX;\r
+          *kbAvail=_AX;\r
+     }\r
+     \r
+     char AllocXMS(unsigned long numberBytes)\r
+     {\r
+          /*\r
+               Allocate a block of XMS memory numberBytes long\r
+          */\r
+     \r
+          _DX = (int)(numberBytes / 1024);\r
+          _AH = 9;\r
+          (*XMSFunc)();\r
+          if (_AX==0)\r
+          {\r
+               return FALSE;\r
+          }\r
+          XMSHandle=_DX;\r
+          return TRUE;\r
+     }\r
+     \r
+     void XMS_free(void)\r
+     {\r
+          /*\r
+               Free used XMS\r
+          */\r
+          _DX=XMSHandle;\r
+          _AH=0x0A;\r
+          (*XMSFunc)();\r
+     }\r
+     \r
+     char XMS_write(unsigned long loc, char far *val, unsigned length)\r
+     {\r
+          /*\r
+               Round length up to next even value\r
+          */\r
+          length += length % 2;\r
+     \r
+          XMSParms.sourceHandle=0;\r
+          XMSParms.sourcePtr=val;\r
+          XMSParms.destHandle=XMSHandle;\r
+          XMSParms.destPtr=(void far *) (loc);\r
+          XMSParms.blockLength=length;  /* Must be an even number! */\r
+          _SI = FP_OFF(&XMSParms);\r
+          _AH=0x0B;\r
+          (*XMSFunc)();\r
+          if (_AX==0)\r
+          {\r
+               return FALSE;\r
+          }\r
+          return TRUE;\r
+     }\r
+     \r
+     \r
+     void *XMS_read(unsigned long loc,unsigned length)\r
+     {\r
+          /*\r
+               Returns pointer to data\r
+               or NULL on error\r
+          */\r
+     \r
+          /*\r
+               Round length up to next even value\r
+          */\r
+          length += length % 2;\r
+     \r
+          XMSParms.sourceHandle=XMSHandle;\r
+          XMSParms.sourcePtr=(void far *) (loc);\r
+          XMSParms.destHandle=0;\r
+          XMSParms.destPtr=XMSBuf;\r
+          XMSParms.blockLength=length;  /* Must be an even number */\r
+          _SI=FP_OFF(&XMSParms);\r
+          _AH=0x0B;\r
+          (*XMSFunc)();\r
+          if (_AX==0)\r
+          {\r
+               return NULL;\r
+          }\r
+          return XMSBuf;\r
+     }\r
+     \r
+     \r
+     /*\r
+          Demonstration code\r
+          Read various length strings into a single XMS block (EMB)\r
+          and write them out again\r
+     */\r
+     \r
+     int main()\r
+     {\r
+          int kbAvail,largestAvail;\r
+          char buffer[80];\r
+          char *p;\r
+          long pos;\r
+          long end;\r
+     \r
+          if (XMS_init() == 0)\r
+               printf("XMS Available ...\n");\r
+          else\r
+          {\r
+               printf("XMS Not Available\n");\r
+               return(1);\r
+          }\r
+     \r
+          XMSSize(&kbAvail,&largestAvail);\r
+          printf("Kilobytes Available: %d; Largest block:\r
+     %dK\n",kbAvail,largestAvail);\r
+     \r
+          if (!AllocXMS(2000 * 1024L))\r
+               return(1);\r
+     \r
+     \r
+          pos = 0;\r
+     \r
+          do\r
+          {\r
+               p = fgets(buffer,1000,stdin);\r
+               if (p != NULL)\r
+               {\r
+                    XMS_write(pos,buffer,strlen(buffer) + 1);\r
+                    pos += strlen(buffer) + 1;\r
+               }\r
+          }\r
+          while(p != NULL);\r
+     \r
+          end = pos;\r
+     \r
+          pos = 0;\r
+     \r
+          do\r
+          {\r
+               memcpy(buffer,XMS_read(pos,100),70);\r
+               printf("%s",buffer);\r
+               pos += strlen(buffer) + 1;\r
+          }\r
+          while(pos < end);\r
+     \r
+          /*\r
+               It is VERY important to free any XMS before exiting!\r
+          */\r
+          XMS_free();\r
+          return 0;\r
+     }\r
+\r
+                                    \r
+                                    \r
+                                    \r
+                             TSR PROGRAMMING\r
+\r
+\r
+Programs which remain running and resident in memory while other programs\r
+are running are the most exciting line of programming for many PC\r
+developers. This type of program is known as a "Terminate and Stay\r
+Resident" or "TSR" program and they are very difficult to program\r
+sucessfuly.\r
+\r
+The difficulties in programming TSRs comes from the limitations of DOS\r
+which is not a multi-tasking operating system, and does not react well to\r
+re-enterant code. That is it's own functions (interrupts) calling\r
+themselves.\r
+\r
+In theory a TSR is quite simple. It is an ordinary program which\r
+terminates not through the usual DOS terminate function, but through the\r
+DOS "keep" function - interrupt 27h. This function reserves an area of\r
+memory, used by the program so that no other programs will overwrite it.\r
+This in itself is not a very difficult task, excepting that the program\r
+needs to tell DOS how much memory to leave it!\r
+\r
+The problems stem mainly from not being able to use DOS function calls\r
+within the TSR program once it has "gone resident".\r
+\r
+There are a few basic rules which help to clarify the problems\r
+encountered in programming TSRs:\r
+\r
+  1.Avoid DOS function calls\r
+  2.Monitor the DOS busy flag, when this flag is nonzero, DOS is\r
+     executing an interrupt 21h function and MUST NOT be disturbed!\r
+  3.Monitor interrupt 28h. This reveals when DOS is busy waiting for\r
+     console input. At this time you can disturb DOS regardless of the\r
+     DOS busy flag setting.\r
+  4.Provide some way of checking whether the TSR is already loaded to\r
+     prevent multiple copies occuring in memory.\r
+  5.Remember that other TSR programs may be chained to interrupts, and\r
+     so you must chain any interrupt vectors that your program needs.\r
+  6.Your TSR program must use its own stack, and NOT that of the running\r
+     process.\r
+  7.TSR programs must be compiled in a small memory model with stack\r
+     checking turned off.\r
+  8.When control passes to your TSR program, it must tell DOS that the\r
+     active process has changed.\r
+\r
+\r
+The following three source code modules describe a complete TSR program.\r
+This is a useful pop-up address book database which can be activated\r
+while any other program is running by pressing the key combination `Alt'\r
+and `.'.  If the address book does not respond to the key press, it is\r
+probably because DOS cannot be disturbed, and you should try to pop-it-up\r
+again.\r
+\r
+\r
+     /*\r
+        A practical TSR program (a pop-up address book database)\r
+        Compile in small memory model with stack checking OFF\r
+     */\r
+     \r
+     #include <dos.h>\r
+     #include <stdio.h>\r
+     #include <string.h>\r
+     #include <dir.h>\r
+     \r
+     static union REGS rg;\r
+     \r
+     /*\r
+        Size of the program to remain resident\r
+        experimentation is required to make this as small as possible\r
+     */\r
+     unsigned sizeprogram = 28000/16;\r
+     \r
+     /* Activate with Alt . */\r
+     unsigned scancode = 52;  /* . */\r
+     unsigned keymask = 8;         /* ALT */\r
+     \r
+     char signature[]= "POPADDR";\r
+     char fpath[40];\r
+     \r
+     /*\r
+          Function prototypes\r
+     */\r
+     \r
+     void curr_cursor(int *x, int *y);\r
+     int resident(char *, void interrupt(*)());\r
+     void resinit(void);\r
+     void terminate(void);\r
+     void restart(void);\r
+     void wait(void);\r
+     void resident_psp(void);\r
+     void exec(void);\r
+     \r
+     /*\r
+        Entry point from DOS\r
+     */\r
+     \r
+     main(int argc, char *argv[])\r
+     {\r
+          void interrupt ifunc();\r
+          int ivec;\r
+     \r
+          /*\r
+             For simplicity, assume the data file is in the root\r
+     directory\r
+             of drive C:\r
+          */\r
+          strcpy(fpath,"C:\\ADDRESS.DAT");\r
+     \r
+          if ((ivec = resident(signature,ifunc)) != 0)\r
+          {\r
+               /* TSR is resident */\r
+               if (argc > 1)\r
+               {\r
+                    rg.x.ax = 0;\r
+                    if (strcmp(argv[1],"quit") == 0)\r
+                         rg.x.ax = 1;\r
+                    else if (strcmp(argv[1],"restart") == 0)\r
+                         rg.x.ax = 2;\r
+                    else if (strcmp(argv[1],"wait") == 0)\r
+                         rg.x.ax = 3;\r
+                    if (rg.x.ax)\r
+                    {\r
+                         int86(ivec,&rg,&rg);\r
+                         return;\r
+                    }\r
+               }\r
+               printf("\nPopup Address Book is already resident");\r
+          }\r
+          else\r
+          {\r
+               /* Initial load of TSR program */\r
+               printf("Popup Address Book Resident.\nPress Alt . To\r
+     Activate....\n");\r
+               resinit();\r
+          }\r
+     }\r
+     \r
+     void interrupt ifunc(bp,di,si,ds,es,dx,cx,bx,ax)\r
+     {\r
+          if(ax == 1)\r
+               terminate();\r
+          else if(ax == 2)\r
+               restart();\r
+          else if(ax == 3)\r
+               wait();\r
+     }\r
+     \r
+     popup()\r
+     {\r
+          int x,y;\r
+     \r
+          curr_cursor(&x,&y);\r
+     \r
+          /* Call the TSR C program here */\r
+          exec();\r
+          cursor(x,y);\r
+     }\r
+     \r
+     /*\r
+          Second source module\r
+     */\r
+     \r
+     #include <dos.h>\r
+     #include <stdio.h>\r
+     \r
+     static union REGS rg;\r
+     static struct SREGS seg;\r
+     static unsigned mcbseg;\r
+     static unsigned dosseg;\r
+     static unsigned dosbusy;\r
+     static unsigned enddos;\r
+     char far *intdta;\r
+     static unsigned intsp;\r
+     static unsigned intss;\r
+     static char far *mydta;\r
+     static unsigned myss;\r
+     static unsigned stack;\r
+     static unsigned ctrl_break;\r
+     static unsigned mypsp;\r
+     static unsigned intpsp;\r
+     static unsigned pids[2];\r
+     static int pidctr = 0;\r
+     static int pp;\r
+     static void interrupt (*oldtimer)();\r
+     static void interrupt (*old28)();\r
+     static void interrupt (*oldkb)();\r
+     static void interrupt (*olddisk)();\r
+     static void interrupt (*oldcrit)();\r
+     \r
+     void interrupt newtimer();\r
+     void interrupt new28();\r
+     void interrupt newkb();\r
+     void interrupt newdisk();\r
+     void interrupt newcrit();\r
+     \r
+     extern unsigned sizeprogram;\r
+     extern unsigned scancode;\r
+     extern unsigned keymask;\r
+     \r
+     static int resoff = 0;\r
+     static int running = 0;\r
+     static int popflg = 0;\r
+     static int diskflag = 0;\r
+     static int kbval;\r
+     static int cflag;\r
+     \r
+     void dores(void);\r
+     void pidaddr(void);\r
+     \r
+     void resinit()\r
+     {\r
+          segread(&seg);\r
+          myss = seg.ss;\r
+     \r
+          rg.h.ah = 0x34;\r
+          intdos(&rg,&rg);\r
+          dosseg = _ES;\r
+          dosbusy = rg.x.bx;\r
+     \r
+          mydta = getdta();\r
+          pidaddr();\r
+          oldtimer = getvect(0x1c);\r
+          old28 = getvect(0x28);\r
+          oldkb = getvect(9);\r
+          olddisk = getvect(0x13);\r
+     \r
+          setvect(0x1c,newtimer);\r
+          setvect(9,newkb);\r
+          setvect(0x28,new28);\r
+          setvect(0x13,newdisk);\r
+     \r
+          stack = (sizeprogram - (seg.ds - seg.cs)) * 16 - 300;\r
+          rg.x.ax = 0x3100;\r
+          rg.x.dx = sizeprogram;\r
+          intdos(&rg,&rg);\r
+     }\r
+     \r
+     void interrupt newdisk(bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,flgs)\r
+     {\r
+          diskflag++;\r
+          (*olddisk)();\r
+          ax = _AX;\r
+          newcrit();\r
+          flgs = cflag;\r
+          --diskflag;\r
+     }\r
+     \r
+     void interrupt newcrit(bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,flgs)\r
+     {\r
+          ax = 0;\r
+          cflag = flgs;\r
+     }\r
+     \r
+     void interrupt newkb()\r
+     {\r
+          if (inportb(0x60) == scancode)\r
+          {\r
+               kbval = peekb(0,0x417);\r
+               if (!resoff && ((kbval & keymask) ^ keymask) == 0)\r
+               {\r
+                    kbval = inportb(0x61);\r
+                    outportb(0x61,kbval | 0x80);\r
+                    outportb(0x61,kbval);\r
+                    disable();\r
+                    outportb(0x20,0x20);\r
+                    enable();\r
+                    if (!running)\r
+                         popflg = 1;\r
+                    return;\r
+               }\r
+          }\r
+          (*oldkb)();\r
+     }\r
+     \r
+     void interrupt newtimer()\r
+     {\r
+          (*oldtimer)();\r
+          if (popflg && peekb(dosseg,dosbusy) == 0)\r
+               if(diskflag == 0)\r
+               {\r
+                    outportb(0x20,0x20);\r
+                    popflg = 0;\r
+                    dores();\r
+               }\r
+     }\r
+     \r
+     void interrupt new28()\r
+     {\r
+          (*old28)();\r
+          if (popflg && peekb(dosseg,dosbusy) != 0)\r
+          {\r
+               popflg = 0;\r
+               dores();\r
+          }\r
+     }\r
+     \r
+     resident_psp()\r
+     {\r
+          intpsp = peek(dosseg,*pids);\r
+          for(pp = 0; pp < pidctr; pp++)\r
+               poke(dosseg,pids[pp],mypsp);\r
+     }\r
+     \r
+     interrupted_psp()\r
+     {\r
+          for(pp = 0; pp < pidctr; pp++)\r
+               poke(dosseg,pids[pp],intpsp);\r
+     }\r
+     \r
+     void dores()\r
+     {\r
+          running = 1;\r
+          disable();\r
+          intsp = _SP;\r
+          intss = _SS;\r
+          _SP = stack;\r
+          _SS = myss;\r
+          enable();\r
+          oldcrit = getvect(0x24);\r
+          setvect(0x24,newcrit);\r
+          rg.x.ax = 0x3300;\r
+          intdos(&rg,&rg);\r
+          ctrl_break = rg.h.dl;\r
+          rg.x.ax = 0x3301;\r
+          rg.h.dl = 0;\r
+          intdos(&rg,&rg);\r
+          intdta = getdta();\r
+          setdta(mydta);\r
+          resident_psp();\r
+          popup();\r
+          interrupted_psp();\r
+          setdta(intdta);\r
+          setvect(0x24,oldcrit);\r
+          rg.x.ax = 0x3301;\r
+          rg.h.dl = ctrl_break;\r
+          intdos(&rg,&rg);\r
+          disable();\r
+          _SP = intsp;\r
+          _SS = intss;\r
+          enable();\r
+          running = 0;\r
+     }\r
+     \r
+     static int avec = 0;\r
+     \r
+     unsigned resident(char *signature,void interrupt(*ifunc)())\r
+     {\r
+          char *sg;\r
+          unsigned df;\r
+          int vec;\r
+     \r
+          segread(&seg);\r
+          df = seg.ds-seg.cs;\r
+          for(vec = 0x60; vec < 0x68; vec++)\r
+          {\r
+               if (getvect(vec) == NULL)\r
+               {\r
+                    if (!avec)\r
+                         avec = vec;\r
+                    continue;\r
+               }\r
+               for(sg = signature; *sg; sg++)\r
+               if (*sg != peekb(peek(0,2+vec*4)+df,(unsigned)sg))\r
+                    break;\r
+               if (!*sg)\r
+                    return vec;\r
+          }\r
+          if (avec)\r
+               setvect(avec,ifunc);\r
+          return 0;\r
+     }\r
+     \r
+     static void pidaddr()\r
+     {\r
+          unsigned adr = 0;\r
+     \r
+          rg.h.ah = 0x51;\r
+          intdos(&rg,&rg);\r
+          mypsp = rg.x.bx;\r
+          rg.h.ah = 0x52;\r
+          intdos(&rg,&rg);\r
+          enddos = _ES;\r
+          enddos = peek(enddos,rg.x.bx-2);\r
+          while(pidctr < 2 && (unsigned)((dosseg<<4) + adr) < (enddos\r
+     <<4))\r
+          {\r
+               if (peek(dosseg,adr) == mypsp)\r
+               {\r
+                    rg.h.ah = 0x50;\r
+                    rg.x.bx = mypsp + 1;\r
+                    intdos(&rg,&rg);\r
+                    if (peek(dosseg,adr) == mypsp + 1)\r
+                         pids[pidctr++] = adr;\r
+                    rg.h.ah = 0x50;\r
+                    rg.x.bx = mypsp;\r
+                    intdos(&rg,&rg);\r
+               }\r
+               adr++;\r
+          }\r
+     }\r
+     \r
+     static resterm()\r
+     {\r
+          setvect(0x1c,oldtimer);\r
+          setvect(9,oldkb);\r
+          setvect(0x28,old28);\r
+          setvect(0x13,olddisk);\r
+          setvect(avec,(void interrupt (*)()) 0);\r
+          rg.h.ah = 0x52;\r
+          intdos(&rg,&rg);\r
+          mcbseg = _ES;\r
+          mcbseg = peek(mcbseg,rg.x.bx-2);\r
+          segread(&seg);\r
+          while(peekb(mcbseg,0) == 0x4d)\r
+          {\r
+               if(peek(mcbseg,1) == mypsp)\r
+               {\r
+                    rg.h.ah = 0x49;\r
+                    seg.es = mcbseg+1;\r
+                    intdosx(&rg,&rg,&seg);\r
+               }\r
+               mcbseg += peek(mcbseg,3) + 1;\r
+          }\r
+     }\r
+     \r
+     terminate()\r
+     {\r
+          if (getvect(0x13) == (void interrupt (*)()) newdisk)\r
+               if (getvect(9) == newkb)\r
+                    if(getvect(0x28) == new28)\r
+                         if(getvect(0x1c) == newtimer)\r
+                         {\r
+                              resterm();\r
+                              return;\r
+                         }\r
+          resoff = 1;\r
+     }\r
+     \r
+     restart()\r
+     {\r
+          resoff = 0;\r
+     }\r
+     \r
+     wait()\r
+     {\r
+          resoff = 1;\r
+     }\r
+     \r
+     void cursor(int y, int x)\r
+     {\r
+          rg.x.ax = 0x0200;\r
+          rg.x.bx = 0;\r
+          rg.x.dx = ((y << 8) & 0xff00) + x;\r
+          int86(16,&rg,&rg);\r
+     }\r
+     \r
+     void curr_cursor(int *y, int *x)\r
+     {\r
+          rg.x.ax = 0x0300;\r
+          rg.x.bx = 0;\r
+          int86(16,&rg,&rg);\r
+          *x = rg.h.dl;\r
+          *y = rg.h.dh;\r
+     }\r
+     \r
+     /*\r
+        Third module, the simple pop-up address book\r
+        with mouse support\r
+     */\r
+     \r
+     #include <stdio.h>\r
+     #include <stdlib.h>\r
+     #include <io.h>\r
+     #include <string.h>\r
+     #include <fcntl.h>\r
+     #include <sys\stat.h>\r
+     #include <dos.h>\r
+     #include <conio.h>\r
+     #include <graphics.h>\r
+     #include <bios.h>\r
+     \r
+     /* left cannot be less than 3 */\r
+     #define left   4\r
+     \r
+     /* Data structure for records */\r
+     typedef struct\r
+     {\r
+          char name[31];\r
+          char company[31];\r
+          char address[31];\r
+          char area[31];\r
+          char town[31];\r
+          char county[31];\r
+          char post[13];\r
+          char telephone[16];\r
+          char fax[16];\r
+     }\r
+     data;\r
+     \r
+     extern char fpath[];\r
+     \r
+     static char scr[4000];\r
+     \r
+     static char sbuff[2000];\r
+     char stext[30];\r
+     data rec;\r
+     int handle;\r
+     int recsize;\r
+     union REGS inreg,outreg;\r
+     \r
+     /*\r
+          Function prototypes\r
+     */\r
+     void FATAL(char *);\r
+     void OPENDATA(void);\r
+     void CONTINUE(void);\r
+     void EXPORT_MULTI(void);\r
+     void GETDATA(int);\r
+     int GETOPT(void);\r
+     void DISPDATA(void);\r
+     void ADD_REC(void);\r
+     void PRINT_MULTI(void);\r
+     void SEARCH(void);\r
+     void MENU(void);\r
+     \r
+     int GET_MOUSE(int *buttons)\r
+     {\r
+          inreg.x.ax = 0;\r
+          int86(0x33,&inreg,&outreg);\r
+          *buttons = outreg.x.bx;\r
+          return outreg.x.ax;\r
+     }\r
+     \r
+     void MOUSE_CURSOR(int status)\r
+     {\r
+          /* Status = 0 cursor off */\r
+          /*          1 cursor on */\r
+     \r
+          inreg.x.ax = 2 - status;\r
+          int86(0x33,&inreg,&outreg);\r
+     }\r
+     \r
+     int MOUSE_LOCATION(int *x, int *y)\r
+     {\r
+          inreg.x.ax = 3;\r
+          int86(0x33,&inreg,&outreg);\r
+     \r
+          *x = outreg.x.cx / 8;\r
+          *y = outreg.x.dx / 8;\r
+     \r
+          return outreg.x.bx;\r
+     }\r
+     \r
+     int GETOPT()\r
+     {\r
+          int result;\r
+          int x;\r
+          int y;\r
+     \r
+          do\r
+          {\r
+               do\r
+               {\r
+                    result = MOUSE_LOCATION(&x,&y);\r
+                    if (result & 1)\r
+                    {\r
+                         if (x >= 52 && x <= 53 && y >= 7 && y <= 15)\r
+                              return y - 7;\r
+                         if (x >= 4 && x <= 40 && y >= 7 && y <= 14)\r
+                              return y + 10;\r
+     \r
+                         if (x >= 4 && x <= 40 && y == 15)\r
+                              return y + 10;\r
+                    }\r
+               }\r
+               while(!bioskey(1));\r
+     \r
+               result = bioskey(0);\r
+               x = result & 0xff;\r
+               if (x == 0)\r
+               {\r
+                    result = result >> 8;\r
+                    result -= 60;\r
+               }\r
+          }\r
+          while(result < 0 || result > 8);\r
+          return result;\r
+     }\r
+     \r
+     void setvideo(unsigned char mode)\r
+     {\r
+          /* Sets the video display mode     and clears the screen */\r
+     \r
+          inreg.h.al = mode;\r
+          inreg.h.ah = 0x00;\r
+          int86(0x10, &inreg, &outreg);\r
+     }\r
+     \r
+     \r
+     int activepage(void)\r
+     {\r
+          /* Returns the currently selected video display page */\r
+     \r
+          union REGS inreg,outreg;\r
+     \r
+          inreg.h.ah = 0x0F;\r
+          int86(0x10, &inreg, &outreg);\r
+          return(outreg.h.bh);\r
+     }\r
+     \r
+     void print(char *str)\r
+     {\r
+          /*\r
+             Prints characters only directly to the current display page\r
+             starting at the current cursor position. The cursor is not\r
+             advanced.\r
+             This function assumes a colour display card. For use with a\r
+             monochrome display card change 0xB800 to read 0xB000\r
+          */\r
+     \r
+          int page;\r
+          int offset;\r
+          unsigned row;\r
+          unsigned col;\r
+          char far *ptr;\r
+     \r
+          page = activepage();\r
+          curr_cursor(&row,&col);\r
+     \r
+          offset = page * 4000 + row * 160 + col * 2;\r
+     \r
+          ptr = MK_FP(0xB800,offset);\r
+     \r
+          while(*str)\r
+          {\r
+               *ptr++= *str++;\r
+               ptr++;\r
+          }\r
+     }\r
+     \r
+     \r
+     void TRUESHADE(int lef, int top, int right, int bottom)\r
+     {\r
+          int n;\r
+     \r
+          /* True Shading of a screen block */\r
+     \r
+          gettext(lef,top,right,bottom,sbuff);\r
+          for(n = 1; n < 2000; n+= 2)\r
+               sbuff[n] = 7;\r
+          puttext(lef,top,right,bottom,sbuff);\r
+     }\r
+     \r
+     void DBOX(int l, int t, int r, int b)\r
+     {\r
+          /* Draws a double line box around the described area */\r
+     \r
+          int n;\r
+     \r
+          cursor(t,l);\r
+          print("\90");\r
+          for(n = 1; n < r - l; n++)\r
+          {\r
+               cursor(t,l + n);\r
+               print("I");\r
+          }\r
+          cursor(t,r);\r
+          print("¯");\r
+     \r
+          for (n = t + 1; n < b; n++)\r
+          {\r
+               cursor(n,l);\r
+               print("§");\r
+               cursor(n,r);\r
+               print("§");\r
+          }\r
+          cursor(b,l);\r
+          print("E");\r
+          for(n = 1; n < r - l; n++)\r
+          {\r
+               cursor(b,l+n);\r
+               print("I");\r
+          }\r
+          cursor(b,r);\r
+          print("¬");\r
+     }\r
+     \r
+     int INPUT(char *text,unsigned length)\r
+     {\r
+          /* Receive a string from the operator */\r
+     \r
+          unsigned key_pos;\r
+          int key;\r
+          unsigned start_row;\r
+          unsigned start_col;\r
+          unsigned end;\r
+          char temp[80];\r
+          char *p;\r
+     \r
+          curr_cursor(&start_row,&start_col);\r
+     \r
+          key_pos = 0;\r
+          end = strlen(text);\r
+          for(;;)\r
+          {\r
+               key = bioskey(0);\r
+               if ((key & 0xFF) == 0)\r
+               {\r
+                    key = key >> 8;\r
+                    if (key == 79)\r
+                    {\r
+                         while(key_pos < end)\r
+                              key_pos++;\r
+                         cursor(start_row,start_col + key_pos);\r
+                    }\r
+                    else\r
+                    if (key == 71)\r
+                    {\r
+                         key_pos = 0;\r
+                         cursor(start_row,start_col);\r
+                    }\r
+                    else\r
+                    if ((key == 75) && (key_pos > 0))\r
+                    {\r
+                         key_pos--;\r
+                         cursor(start_row,start_col + key_pos);\r
+                    }\r
+                    else\r
+                    if ((key == 77) && (key_pos < end))\r
+                    {\r
+                         key_pos++;\r
+                         cursor(start_row,start_col + key_pos);\r
+                    }\r
+                    else\r
+                    if (key == 83)\r
+                    {\r
+                         p = text + key_pos;\r
+                         while(*(p+1))\r
+                         {\r
+                              *p = *(p+1);\r
+                              p++;\r
+                         }\r
+                         *p = 32;\r
+                         if (end > 0)\r
+                              end--;\r
+                         cursor(start_row,start_col);\r
+                         cprintf(text);\r
+                         cprintf(" ");\r
+                         if ((key_pos > 0) && (key_pos == end))\r
+                              key_pos--;\r
+                         cursor(start_row,start_col + key_pos);\r
+                    }\r
+               }\r
+               else\r
+               {\r
+                    key = key & 0xFF;\r
+                    if (key == 13 || key == 27)\r
+                         break;\r
+                    else\r
+                    if ((key == 8) && (key_pos > 0))\r
+                    {\r
+                         end--;\r
+                         key_pos--;\r
+                         text[key_pos--] = '\0';\r
+                         strcpy(temp,text);\r
+                         p = text + key_pos + 2;\r
+                         strcat(temp,p);\r
+                         strcpy(text,temp);\r
+                         cursor(start_row,start_col);\r
+                         cprintf("%-*.*s",length,length,text);\r
+                         key_pos++;\r
+                         cursor(start_row,start_col + key_pos);\r
+                    }\r
+                    else\r
+                    if ((key > 31) && (key_pos < length)  &&\r
+                       (start_col + key_pos < 80))\r
+                    {\r
+                         if (key_pos <= end)\r
+                         {\r
+                              p = text + key_pos;\r
+                              memmove(p+1,p,end - key_pos);\r
+                              if (end < length)\r
+                                   end++;\r
+                              text[end] = '\0';\r
+                         }\r
+                         text[key_pos++] = (char)key;\r
+                         if (key_pos > end)\r
+                         {\r
+                              end++;\r
+                              text[end] = '\0';\r
+                         }\r
+                         cursor(start_row,start_col);\r
+                         cprintf("%-*.*s",length,length,text);\r
+                         cursor(start_row,start_col + key_pos);\r
+                    }\r
+               }\r
+          }\r
+          text[end] = '\0';\r
+          return key;\r
+     }\r
+     \r
+     void FATAL(char *error)\r
+     {\r
+          /* A fatal error has occured */\r
+     \r
+          printf("\nFATAL ERROR: %s",error);\r
+          exit(0);\r
+     }\r
+     \r
+     void OPENDATA()\r
+     {\r
+          /* Check for existence of data file and if not create it */\r
+          /* otherwise open it for reading/writing at end of file */\r
+     \r
+          handle = open(fpath,O_RDWR,S_IWRITE);\r
+     \r
+          if (handle == -1)\r
+          {\r
+               handle = open(fpath,O_RDWR|O_CREAT,S_IWRITE);\r
+               if (handle == -1)\r
+                    FATAL("Unable to create data file");\r
+          }\r
+          /* Read in first rec */\r
+          read(handle,&rec,recsize);\r
+     }\r
+     \r
+     void CLOSEDATA()\r
+     {\r
+          close(handle);\r
+     }\r
+     \r
+     void GETDATA(int start)\r
+     {\r
+          /* Get address data from operator */\r
+     \r
+          textcolor(BLACK);\r
+          textbackground(GREEN);\r
+          gotoxy(left,8);\r
+          print("Name ");\r
+          gotoxy(left,9);\r
+          print("Company ");\r
+          gotoxy(left,10);\r
+          print("Address ");\r
+          gotoxy(left,11);\r
+          print("Area ");\r
+          gotoxy(left,12);\r
+          print("Town ");\r
+          gotoxy(left,13);\r
+          print("County ");\r
+          gotoxy(left,14);\r
+          print("Post Code ");\r
+          gotoxy(left,15);\r
+          print("Telephone ");\r
+          gotoxy(left,16);\r
+          print("Fax ");\r
+     \r
+          switch(start)\r
+          {\r
+               case 0: gotoxy(left + 10,8);\r
+                    if(INPUT(rec.name,30) == 27)\r
+                         break;\r
+               case 1: gotoxy(left + 10,9);\r
+                    if(INPUT(rec.company,30) == 27)\r
+                         break;\r
+               case 2: gotoxy(left + 10,10);\r
+                    if(INPUT(rec.address,30) == 27)\r
+                         break;\r
+               case 3: gotoxy(left + 10,11);\r
+                    if(INPUT(rec.area,30) == 27)\r
+                         break;\r
+               case 4: gotoxy(left + 10,12);\r
+                    if(INPUT(rec.town,30) == 27)\r
+                         break;\r
+               case 5: gotoxy(left + 10,13);\r
+                    if(INPUT(rec.county,30) == 27)\r
+                         break;\r
+               case 6: gotoxy(left + 10,14);\r
+                    if(INPUT(rec.post,12) == 27)\r
+                         break;\r
+               case 7: gotoxy(left + 10,15);\r
+                    if(INPUT(rec.telephone,15) == 27)\r
+                         break;\r
+               case 8: gotoxy(left + 10,16);\r
+                    INPUT(rec.fax,15);\r
+                    break;\r
+          }\r
+          textcolor(WHITE);\r
+          textbackground(RED);\r
+          gotoxy(left + 23,21);\r
+          print("                                                 ");\r
+     }\r
+     \r
+     void DISPDATA()\r
+     {\r
+          /* Display address data */\r
+          textcolor(BLACK);\r
+          textbackground(GREEN);\r
+          cursor(7,3);\r
+          cprintf("Name    %-30.30s",rec.name);\r
+          cursor(8,3);\r
+          cprintf("Company   %-30.30s",rec.company);\r
+          cursor(9,3);\r
+          cprintf("Address   %-30.30s",rec.address);\r
+          cursor(10,3);\r
+          cprintf("Area    %-30.30s",rec.area);\r
+          cursor(11,3);\r
+          cprintf("Town    %-30.30s",rec.town);\r
+          cursor(12,3);\r
+          cprintf("County     %-30.30s",rec.county);\r
+          cursor(13,3);\r
+          cprintf("Post Code %-30.30s",rec.post);\r
+          cursor(14,3);\r
+          cprintf("Telephone %-30.30s",rec.telephone);\r
+          cursor(15,3);\r
+          cprintf("Fax      %-30.30s",rec.fax);\r
+     }\r
+     \r
+     int LOCATE(char *text)\r
+     {\r
+          int result;\r
+     \r
+          do\r
+          {\r
+               /* Read rec into memory */\r
+               result = read(handle,&rec,recsize);\r
+               if (result > 0)\r
+               {\r
+                    /* Scan rec for matching data */\r
+                    if (strstr(strupr(rec.name),text) != NULL)\r
+                         return(1);\r
+                    if (strstr(strupr(rec.company),text) != NULL)\r
+                         return(1);\r
+                    if (strstr(strupr(rec.address),text) != NULL)\r
+                         return(1);\r
+                    if (strstr(strupr(rec.area),text) != NULL)\r
+                         return(1);\r
+                    if (strstr(strupr(rec.town),text) != NULL)\r
+                         return(1);\r
+                    if (strstr(strupr(rec.county),text) != NULL)\r
+                         return(1);\r
+                    if (strstr(strupr(rec.post),text) != NULL)\r
+                         return(1);\r
+                    if (strstr(strupr(rec.telephone),text) != NULL)\r
+                         return(1);\r
+                    if (strstr(strupr(rec.fax),text) != NULL)\r
+                         return(1);\r
+               }\r
+          }\r
+          while(result > 0);\r
+          return(0);\r
+     }\r
+     \r
+     void SEARCH()\r
+     {\r
+          int result;\r
+     \r
+          gotoxy(left,21);\r
+          textcolor(WHITE);\r
+          textbackground(RED);\r
+          cprintf("Enter data to search for ");\r
+          strcpy(stext,"");\r
+          INPUT(stext,30);\r
+          if (*stext == 0)\r
+          {\r
+               gotoxy(left,21);\r
+               cprintf("%70c",32);\r
+               return;\r
+          }\r
+          gotoxy(left,21);\r
+          textcolor(WHITE);\r
+          textbackground(RED);\r
+          cprintf("Searching for %s Please Wait....",stext);\r
+          strupr(stext);\r
+          /* Locate start of file */\r
+          lseek(handle,0,SEEK_SET);\r
+          result = LOCATE(stext);\r
+          if (result == 0)\r
+          {\r
+               gotoxy(left,21);\r
+               cprintf("%70c",32);\r
+               gotoxy(left + 27,21);\r
+               cprintf("NO MATCHING RECORDS");\r
+               gotoxy(left + 24,22);\r
+               cprintf("Press RETURN to Continue");\r
+               bioskey(0);\r
+               gotoxy(left,21);\r
+               cprintf("%70c",32);\r
+               gotoxy(left,22);\r
+               cprintf("%70c",32);\r
+          }\r
+          else\r
+          {\r
+               lseek(handle,0 - recsize,SEEK_CUR);\r
+               read(handle,&rec,recsize);\r
+               DISPDATA();\r
+          }\r
+          textcolor(WHITE);\r
+          textbackground(RED);\r
+          gotoxy(left,21);\r
+          cprintf("%70c",32);\r
+          textcolor(BLACK);\r
+          textbackground(GREEN);\r
+     }\r
+     \r
+     void CONTINUE()\r
+     {\r
+          int result;\r
+          long curpos;\r
+     \r
+          curpos = tell(handle) - recsize;\r
+     \r
+          result = LOCATE(stext);\r
+          textcolor(WHITE);\r
+          textbackground(RED);\r
+          if (result == 0)\r
+          {\r
+               gotoxy(left + 24,21);\r
+               cprintf("NO MORE MATCHING RECORDS");\r
+               gotoxy(left + 24,22);\r
+               cprintf("Press RETURN to Continue");\r
+               bioskey(0);\r
+               gotoxy(left,21);\r
+               cprintf("%70c",32);\r
+               gotoxy(left,22);\r
+               cprintf("%70c",32);\r
+               lseek(handle,curpos,SEEK_SET);\r
+               read(handle,&rec,recsize);\r
+               DISPDATA();\r
+          }\r
+          else\r
+          {\r
+               lseek(handle,0 - recsize,SEEK_CUR);\r
+               read(handle,&rec,recsize);\r
+               DISPDATA();\r
+          }\r
+          textcolor(WHITE);\r
+          textbackground(RED);\r
+          gotoxy(left,21);\r
+          cprintf("%70c",32);\r
+          gotoxy(left,22);\r
+          cprintf("                                           ");\r
+          textcolor(BLACK);\r
+          textbackground(GREEN);\r
+     }\r
+     \r
+     void PRINT_MULTI()\r
+     {\r
+          data buffer;\r
+          char destination[60];\r
+          char text[5];\r
+          int result;\r
+          int ok;\r
+          int ok2;\r
+          int blanks;\r
+          int total_lines;\r
+          char *p;\r
+          FILE *fp;\r
+     \r
+          textcolor(WHITE);\r
+          textbackground(RED);\r
+          gotoxy(left + 23,21);\r
+          cprintf("Enter selection criteria");\r
+     \r
+          /* Clear existing rec details */\r
+          memset(&rec,0,recsize);\r
+     \r
+          DISPDATA();\r
+          GETDATA(0);\r
+     \r
+          textcolor(WHITE);\r
+          textbackground(RED);\r
+          gotoxy(left,21);\r
+          cprintf("Enter report destination PRN");\r
+          strcpy(destination,"PRN");\r
+          gotoxy(left,22);\r
+          cprintf("Enter Address length in lines 18");\r
+          strcpy(text,"18");\r
+          gotoxy(left + 25,21);\r
+          INPUT(destination,40);\r
+          gotoxy(left +30,22);\r
+          INPUT(text,2);\r
+          gotoxy(left,21);\r
+          cprintf("%72c",32);\r
+          gotoxy(left,22);\r
+          cprintf("%72c",32);\r
+     \r
+          total_lines = atoi(text) - 6;\r
+          if (total_lines < 0)\r
+               total_lines = 0;\r
+     \r
+          fp = fopen(destination,"w+");\r
+          if (fp == NULL)\r
+          {\r
+               gotoxy(left,21);\r
+               cprintf("Unable to print to %s",destination);\r
+               gotoxy(left,22);\r
+               cprintf("Press RETURN to Continue");\r
+               bioskey(0);\r
+               gotoxy(left,21);\r
+               cprintf("%78c",32);\r
+               gotoxy(left,22);\r
+               cprintf("                          ");\r
+          }\r
+     \r
+          /* Locate start of file */\r
+          lseek(handle,0,SEEK_SET);\r
+     \r
+          do\r
+          {\r
+               /* Read rec into memory */\r
+               result = read(handle,&buffer,recsize);\r
+               if (result > 0)\r
+               {\r
+                    ok = 1;\r
+                    /* Scan rec for matching data */\r
+                    if (*rec.name)\r
+                         if (stricmp(buffer.name,rec.name))\r
+                              ok = 0;\r
+                    if (*rec.company)\r
+                         if (stricmp(buffer.company,rec.company))\r
+                              ok = 0;\r
+                    if (*rec.address)\r
+                         if (stricmp(buffer.address,rec.address))\r
+                              ok = 0;\r
+                    if (*rec.area)\r
+                         if (stricmp(buffer.area,rec.area))\r
+                              ok = 0;\r
+                    if (*rec.town)\r
+                         if (stricmp(buffer.town,rec.town))\r
+                              ok = 0;\r
+                    if (*rec.county)\r
+                         if (stricmp(buffer.county,rec.county))\r
+                              ok = 0;\r
+                    if (*rec.post)\r
+                         if (stricmp(buffer.post,rec.post))\r
+                         ok = 0;\r
+                    if (*rec.telephone)\r
+                         if (stricmp(buffer.telephone,rec.telephone))\r
+                              ok = 0;\r
+                    if (*rec.fax)\r
+                         if (stricmp(buffer.fax,rec.fax))\r
+                              ok = 0;\r
+                    if (ok)\r
+                    {\r
+                         blanks = total_lines;\r
+                         p = buffer.name;\r
+                         ok2 = 0;\r
+                         while(*p)\r
+                         {\r
+                              if (*p != 32)\r
+                              {\r
+                                   ok2 = 1;\r
+                                   break;\r
+                              }\r
+                              p++;\r
+                         }\r
+                         if (!ok2)\r
+                              blanks++;\r
+                         else\r
+                              fprintf(fp,"%s\n",buffer.name);\r
+                         p = buffer.company;\r
+                         ok2 = 0;\r
+                         while(*p)\r
+                         {\r
+                              if (*p != 32)\r
+                              {\r
+                                   ok2 = 1;\r
+                                   break;\r
+                              }\r
+                              p++;\r
+                         }\r
+                         if (!ok2)\r
+                              blanks++;\r
+                         else\r
+                              fprintf(fp,"%s\n",buffer.company);\r
+                         p = buffer.address;\r
+                         ok2 = 0;\r
+     \r
+                         while(*p)\r
+                         {\r
+                              if (*p != 32)\r
+                              {\r
+                                   ok2 = 1;\r
+                                   break;\r
+                              }\r
+                              p++;\r
+                         }\r
+                         if (!ok2)\r
+                              blanks++;\r
+                         else\r
+                              fprintf(fp,"%s\n",buffer.address);\r
+                         p = buffer.area;\r
+                         ok2 = 0;\r
+                         while(*p)\r
+                         {\r
+                              if (*p != 32)\r
+                              {\r
+                                   ok2 = 1;\r
+                                   break;\r
+                              }\r
+                              p++;\r
+                         }\r
+                         if (!ok2)\r
+                              blanks++;\r
+                         else\r
+                              fprintf(fp,"%s\n",buffer.area);\r
+                         p = buffer.town;\r
+                         ok2 = 0;\r
+                         while(*p)\r
+                         {\r
+                              if (*p != 32)\r
+                              {\r
+                                   ok2 = 1;\r
+                                   break;\r
+                              }\r
+                              p++;\r
+                         }\r
+                         if (!ok2)\r
+                              blanks++;\r
+                         else\r
+                              fprintf(fp,"%s\n",buffer.town);\r
+                         p = buffer.county;\r
+                         ok2 = 0;\r
+     \r
+                         while(*p)\r
+                         {\r
+                              if (*p != 32)\r
+                              {\r
+                                   ok2 = 1;\r
+                                   break;\r
+                              }\r
+                              p++;\r
+                         }\r
+                         if (!ok2)\r
+                              blanks++;\r
+                         else\r
+                              fprintf(fp,"%s\n",buffer.county);\r
+                         p = buffer.post;\r
+                         ok2 = 0;\r
+                         while(*p)\r
+                         {\r
+                              if (*p != 32)\r
+                              {\r
+                                   ok2 = 1;\r
+                                   break;\r
+                              }\r
+                              p++;\r
+                         }\r
+                         if (!ok2)\r
+                              blanks++;\r
+                         else\r
+                              fprintf(fp,"%s\n",buffer.post);\r
+                         while(blanks)\r
+                         {\r
+                              fprintf(fp,"\n");\r
+                              blanks--;\r
+                         }\r
+                    }\r
+               }\r
+          }\r
+          while(result > 0);\r
+          fclose(fp);\r
+          lseek(handle,0,SEEK_SET);\r
+          read(handle,&rec,recsize);\r
+          DISPDATA();\r
+     }\r
+     \r
+     void EXPORT_MULTI()\r
+     {\r
+          data buffer;\r
+          char destination[60];\r
+          int result;\r
+          int ok;\r
+          FILE *fp;\r
+     \r
+          textcolor(WHITE);\r
+          textbackground(RED);\r
+          gotoxy(left + 23,21);\r
+          cprintf("Enter selection criteria");\r
+     \r
+          /* Clear existing rec details */\r
+          memset(&rec,0,recsize);\r
+     \r
+          DISPDATA();\r
+          GETDATA(0);\r
+     \r
+          textcolor(WHITE);\r
+          textbackground(RED);\r
+          gotoxy(left,21);\r
+          cprintf("Enter export file address.txt");\r
+          strcpy(destination,"address.txt");\r
+          gotoxy(left + 18,21);\r
+          INPUT(destination,59);\r
+          gotoxy(left,21);\r
+          cprintf("%70c",32);\r
+     \r
+          fp = fopen(destination,"w+");\r
+          if (fp == NULL)\r
+          {\r
+               gotoxy(left,21);\r
+               cprintf("Unable to print to %s",destination);\r
+               gotoxy(left,22);\r
+               cprintf("Press RETURN to Continue");\r
+               bioskey(0);\r
+               gotoxy(left,21);\r
+               cprintf("%78c",32);\r
+               gotoxy(left,22);\r
+               cprintf("                          ");\r
+          }\r
+          /* Locate start of file */\r
+          lseek(handle,0,SEEK_SET);\r
+     \r
+          do\r
+          {\r
+               /* Read rec into memory */\r
+               result = read(handle,&buffer,recsize);\r
+               if (result > 0)\r
+               {\r
+                    ok = 1;\r
+                    /* Scan rec for matching data */\r
+                    if (*rec.name)\r
+                         if (stricmp(buffer.name,rec.name))\r
+                              ok = 0;\r
+                    if (*rec.company)\r
+                         if (stricmp(buffer.company,rec.company))\r
+                              ok = 0;\r
+                    if (*rec.address)\r
+                         if (stricmp(buffer.address,rec.address))\r
+                              ok = 0;\r
+                    if (*rec.area)\r
+                         if (stricmp(buffer.area,rec.area))\r
+                              ok = 0;\r
+                    if (*rec.town)\r
+                         if (stricmp(buffer.town,rec.town))\r
+                              ok = 0;\r
+                    if (*rec.county)\r
+                         if (stricmp(buffer.county,rec.county))\r
+                              ok = 0;\r
+                    if (*rec.post)\r
+                         if (stricmp(buffer.post,rec.post))\r
+                         ok = 0;\r
+                    if (*rec.telephone)\r
+                         if (stricmp(buffer.telephone,rec.telephone))\r
+                              ok = 0;\r
+                    if (*rec.fax)\r
+                         if (stricmp(buffer.fax,rec.fax))\r
+                              ok = 0;\r
+                    if (ok)\r
+                    {\r
+                         fprintf(fp,"\"%s\",",buffer.name);\r
+                         fprintf(fp,"\"%s\",",buffer.company);\r
+                         fprintf(fp,"\"%s\",",buffer.address);\r
+                         fprintf(fp,"\"%s\",",buffer.area);\r
+                         fprintf(fp,"\"%s\",",buffer.town);\r
+                         fprintf(fp,"\"%s\",",buffer.county);\r
+                         fprintf(fp,"\"%s\",",buffer.post);\r
+                         fprintf(fp,"\"%s\",",buffer.telephone);\r
+                         fprintf(fp,"\"%s\"\n",buffer.fax);\r
+     \r
+                    }\r
+               }\r
+          }\r
+     \r
+          while(result > 0);\r
+          fclose(fp);\r
+          lseek(handle,0,SEEK_SET);\r
+          read(handle,&rec,recsize);\r
+          DISPDATA();\r
+     }\r
+     \r
+     void MENU()\r
+     {\r
+          int option;\r
+          long result;\r
+          long end;\r
+          int new;\r
+     \r
+          do\r
+          {\r
+               cursor(21,26);\r
+               print("Select option (F2 - F10)");\r
+               cursor(7,52);\r
+               print("F2 Next record");\r
+               cursor(8,52);\r
+               print("F3 Previous record");\r
+               cursor(9,52);\r
+               print("F4 Amend record");\r
+               cursor(10,52);\r
+               print("F5 Add new record");\r
+               cursor(11,52);\r
+               print("F6 Search");\r
+               cursor(12,52);\r
+               print("F7 Continue search");\r
+               cursor(13,52);\r
+               print("F8 Print address labels");\r
+               cursor(14,52);\r
+               print("F9 Export records");\r
+               cursor(15,52);\r
+               print("F10 Exit");\r
+               MOUSE_CURSOR(1);\r
+               option = GETOPT();\r
+               MOUSE_CURSOR(0);\r
+     \r
+               switch(option)\r
+               {\r
+                    case 0 : /* Next rec */\r
+                          result = read(handle,&rec,recsize);\r
+                          if (!result)\r
+                          {\r
+                              lseek(handle,0,SEEK_SET);\r
+                               result = read(handle,&rec,recsize);\r
+                          }\r
+                          DISPDATA();\r
+                          break;\r
+     \r
+                    case 1 : /* Previous rec */\r
+                         result = lseek(handle,0 - recsize * 2,SEEK_CUR);\r
+                         if (result <= -1)\r
+                              lseek(handle,0 - recsize,SEEK_END);\r
+                         result = read(handle,&rec,recsize);\r
+                         DISPDATA();\r
+                         break;\r
+\r
+                    case 3 : /* Add rec */\r
+                          lseek(handle,0,SEEK_END);\r
+                          memset(&rec,0,recsize);\r
+                          DISPDATA();\r
+     \r
+                    case 2 : /* Amend current rec */\r
+                          new = 1;\r
+                          if (*rec.name)\r
+                              new = 0;\r
+                          else\r
+                          if (*rec.company)\r
+                              new = 0;\r
+                          else\r
+                          if (*rec.address)\r
+                              new = 0;\r
+                          else\r
+                          if (*rec.area)\r
+                              new = 0;\r
+                          else\r
+                          if (*rec.town)\r
+                              new = 0;\r
+                          else\r
+                          if (*rec.county)\r
+                              new = 0;\r
+                          else\r
+                          if (*rec.post)\r
+                              new = 0;\r
+                          else\r
+                          if (*rec.telephone)\r
+                              new = 0;\r
+                          else\r
+                          if (*rec.fax)\r
+                              new = 0;\r
+                          result = tell(handle);\r
+                          lseek(handle,0,SEEK_END);\r
+                          end = tell(handle);\r
+     \r
+                          /* Back to original position */\r
+                          lseek(handle,result,SEEK_SET);\r
+     \r
+                          /* If not at end of file, && !new rewind one\r
+rec */\r
+                          if (result != end || ! new)\r
+                              result = lseek(handle,0 -\r
+recsize,SEEK_CUR);\r
+                          result = tell(handle);\r
+                          gotoxy(left + 22,21);\r
+                          print(" Enter address details  ");\r
+                          GETDATA(0);\r
+                          if (*rec.name || *rec.company)\r
+                              result =  write(handle,&rec,recsize);\r
+                          break;\r
+\r
+                    case 4 : /* Search */\r
+                          gotoxy(left + 22,21);\r
+                          print("                           ");\r
+                          SEARCH();\r
+                          break;\r
+     \r
+                    case 5 : /* Continue */\r
+                          gotoxy(left + 22,21);\r
+                          print("                           ");\r
+                          CONTINUE();\r
+                          break;\r
+     \r
+                    case 6 : /* Print */\r
+                          gotoxy(left + 22,21);\r
+                          print("                           ");\r
+                          PRINT_MULTI();\r
+                          break;\r
+\r
+                    case 7 : /* Export */\r
+                          gotoxy(left + 22,21);\r
+                          print("                           ");\r
+                          EXPORT_MULTI();\r
+                          break;\r
+     \r
+                    case 8 : /* Exit */\r
+                          break;\r
+     \r
+                    default: /* Amend current rec */\r
+                          new = 1;\r
+                          if (*rec.name)\r
+                              new = 0;\r
+                          else\r
+                          if (*rec.company)\r
+                              new = 0;\r
+                          else\r
+                          if (*rec.address)\r
+                              new = 0;\r
+                          else\r
+                          if (*rec.area)\r
+                              new = 0;\r
+                          else\r
+                          if (*rec.town)\r
+                              new = 0;\r
+                          else\r
+                          if (*rec.county)\r
+                              new = 0;\r
+                          else\r
+                          if (*rec.post)\r
+                              new = 0;\r
+                          else\r
+                          if (*rec.telephone)\r
+                              new = 0;\r
+                          else\r
+                          if (*rec.fax)\r
+                              new = 0;\r
+                          result = tell(handle);\r
+                          lseek(handle,0,SEEK_END);\r
+                          end = tell(handle);\r
+\r
+                          /* Back to original position */\r
+                          lseek(handle,result,SEEK_SET);\r
+\r
+                          /* If not at end of file, && !new rewind one\r
+rec */\r
+                          if (result != end || ! new)\r
+                              result = lseek(handle,0 -\r
+recsize,SEEK_CUR);\r
+                          result = tell(handle);\r
+                          gotoxy(left + 22,21);\r
+                          print(" Enter address details  ");\r
+                          GETDATA(option - 17);\r
+                          if (*rec.name || *rec.company)\r
+                              result = write(handle,&rec,recsize);\r
+                          option = -1;\r
+                          break;\r
+\r
+               }\r
+          }\r
+     \r
+          while(option != 8);\r
+     }\r
+     \r
+     void exec()\r
+     {\r
+          gettext(1,1,80,25,scr);\r
+          setvideo(3);\r
+          textbackground(WHITE);\r
+          textcolor(BLACK);\r
+          clrscr();\r
+          recsize = sizeof(data);\r
+     \r
+          OPENDATA();\r
+     \r
+          TRUESHADE(left,3,79,5);\r
+          window(left - 2,2 ,78, 4);\r
+          textcolor(YELLOW);\r
+          textbackground(MAGENTA);\r
+          clrscr();\r
+          DBOX(left - 3, 1, 77, 3);\r
+          gotoxy(3,2);\r
+          print("Servile Software             PC ADDRESS BOOK 5.2\r
+     (c) 1994");\r
+     \r
+          TRUESHADE(left,8,left + 43,18);\r
+          window(left - 2,7 , left + 42, 17);\r
+          textcolor(BLACK);\r
+          textbackground(GREEN);\r
+          clrscr();\r
+          DBOX(left - 3, 6, left + 41, 16);\r
+     \r
+          TRUESHADE(left + 48,8,79,18);\r
+          window(left + 46, 7 , 78, 17);\r
+          textbackground(BLUE);\r
+          textcolor(YELLOW);\r
+          clrscr();\r
+          DBOX(left + 45,6,77,16);\r
+     \r
+          TRUESHADE(left ,21,79,24);\r
+          window(left - 2, 20 , 78, 23);\r
+          textbackground(RED);\r
+          textcolor(WHITE);\r
+          clrscr();\r
+          DBOX(left - 3,19,77,22);\r
+     \r
+          window(1,1,80,25);\r
+          textcolor(BLACK);\r
+          textbackground(GREEN);\r
+          DISPDATA();\r
+     \r
+          MENU();\r
+     \r
+          CLOSEDATA();\r
+          puttext(1,1,80,25,scr);\r
+          return;\r
+     }\r
+     \r
+                                    \r
+                       INTERFACING C WITH CLIPPER\r
+\r
+The Clipper programming language is a popular xBase environment for the\r
+PC.  However, it lacks many of the facilities available to programmers of\r
+other languages, and it is quite slow compared to C. Because of this\r
+there are a large number of third party add-on libraries available for\r
+Clipper which provide the facilities lacked.\r
+\r
+As a programmer you probably want to write your own library for Clipper,\r
+or perhaps individual functions to cater for circumstances which Clipper\r
+cannot handle, such as high resolution graphics.\r
+\r
+Throughout this section, Clipper refers to the Summer `87 Clipper\r
+compiler, although initial tests show that the functions described here\r
+work perfectly well with the new Clipper 5 compiler also, we are not in a\r
+position to guarrantee success!\r
+\r
+\r
+COMPILING AND LINKING\r
+The  Clipper extend functions allow user defined functions to be written\r
+in C, linked with and used by the Clipper application. The  first\r
+problem a programmer must address when writing functions in C  to link\r
+with  a  Clipper  application is that of the  C  compiler's  run  time\r
+libraries.\r
+\r
+If one is writing functions with Microsoft C,  then most of the required\r
+run time  library  functions  will be found in the  Clipper.lib  and\r
+Extend.lib libraries which are part of Clipper.\r
+\r
+If,  however, one is using a different C compiler, such as Borland's\r
+Turbo C then the run time library routines must be supplied on the link\r
+line.\r
+\r
+All C functions must be compiled using the large memory model the\r
+following line is used with Microsoft C\r
+\r
+\r
+     cl /c /AL /Zl /Oalt /FPa /Gs <program.c>\r
+\r
+and this compile line may be used with Turbo C\r
+\r
+     tcc -c -ml <program>\r
+\r
+simply substitute <program> for the program name to be compiled.\r
+\r
+Having compiled a C function it must be linked in with the application.\r
+If the C function was compiled with Microsoft C then the link line will\r
+look a little like this;\r
+\r
+\r
+     LINK /SE:500 /NOE program.obj cfunc.obj,,,Clipper Extend\r
+\r
+If  the C function was linked with another C compiler you will also need\r
+to link in the C run time libraries,  for example to link in the Turbo C\r
+large memory mode library use the following link line;\r
+\r
+\r
+     LINK /SE:500 /NOE program.obj cfunc.obj,,,Clipper Extend cl\r
+\r
+If one is using a number of separately compiled C functions it is a good\r
+idea to  collect  them in a library.  If you are using Microsoft C then\r
+you  can simply  create  the  library by using Microsoft Lib.exe with\r
+the  following command line;\r
+\r
+\r
+      LIB mylib +prog1 +prog2, NUL, NUL\r
+\r
+This tells the librarian to add prog1.obj and prog2.obj to a library\r
+called mylib.lib,   creating  it  if it does not exist.  The NUL\r
+parameter  is  for supressing the listing file.\r
+\r
+\r
+If you have been using another C compiler you should copy the C large\r
+memory model run time library before adding your functions to it for\r
+example;\r
+\r
+\r
+      COPY C:\TURBOC\LIB\cl.lib mylib.lib\r
+      LIB mylib +prog1 +prog2, NUL, NUL\r
+\r
+Then when you link your Clipper application you will use a link line\r
+similar to;\r
+\r
+     LINK /SE:500 /NOE myprog,,,Clipper Extend Mylib\r
+\r
+Often  when  linking C functions with  Clipper applications link errors\r
+will occur such as those shown below;\r
+\r
+     \r
+     Microsoft (R) Overlay Linker  Version 3.65\r
+     Copyright (C) Microsoft Corp 1983-1988.  All rights reserved.\r
+     \r
+     \r
+     LINK : error L2029: Unresolved externals:\r
+     \r
+     \r
+     FIWRQQ in file(s):\r
+      M:SLIB.LIB(TEST)\r
+     FIDRQQ in file(s):\r
+      M:SLIB.LIB(TEST)\r
+     \r
+     There were 2 errors detected\r
+     \r
+\r
+Example Link Errors\r
+\r
+The  errors  shown  here  are  `Unresolved externals',   that  is  they\r
+are references to functions which are not found in any of the object\r
+modules  or libraries  specified on the link line.  These occur because\r
+the C  compilers often  scatter  functions and variables through a number\r
+of  libraries.   In tracking  these  functions down  use may be made of\r
+the Microsoft  librarian list file option.  If you run Lib.Exe on the\r
+Turbo C `emu.lib'  library file and specify a listing file as follows;\r
+\r
+\r
+     LIB emu,emu.lst\r
+\r
+The  librarian  will create an ascii file which contains the names  of\r
+each object module contained in the specified library file, and the names\r
+of each function and public variable declared in each object module, as\r
+shown in this listing of Borland's EMU.LIB library.\r
+\r
+\r
+     e086_Entry........EMU086      e086_Shortcut.....EMU086\r
+     e087_Entry........EMU087      e087_Shortcut.....EMU087\r
+     FIARQQ............EMUINIT          FICRQQ............EMUINIT\r
+     FIDRQQ............EMUINIT          FIERQQ............EMUINIT\r
+     FISRQQ............EMUINIT          FIWRQQ............EMUINIT\r
+     FJARQQ............EMUINIT          FJCRQQ............EMUINIT\r
+     FJSRQQ............EMUINIT          __EMURESET........EMUINIT\r
+     \r
+     \r
+     EMUINIT        Offset: 00000010H  Code and data size: 1a2H\r
+     FIARQQ         FICRQQ              FIDRQQ              FIERQQ\r
+     FISRQQ         FIWRQQ              FJARQQ              FJCRQQ\r
+     FJSRQQ         __EMURESET\r
+     \r
+     EMU086         Offset: 00000470H  Code and data size: 2630H\r
+       e086_Entry        e086_Shortcut\r
+     \r
+     EMU087         Offset: 00003200H  Code and data size: 417H\r
+       e087_Entry        e087_Shortcut\r
+     \r
+\r
+\r
+Receiving Parameters\r
+\r
+Clipper provides six different functions for receiving parameters in a C\r
+function. These functions are;\r
+\r
+\r
+  Receive a string      char * _parc(int,[int])\r
+  Receive a Date string char * _pards(int,[int])\r
+  Receive a logical     int _parl(int,[int])\r
+  Receive an integer         int _parni(int,[int])\r
+  Receive a long        long _parnl(int,[int])\r
+  Receive a double      double _parnd(int,[int])\r
+\r
+\r
+\r
+To  illustrate  simple  parameter  receiving in a C  function  I  offer\r
+the following  simple C function which receives two numeric parameters\r
+from  the calling  Clipper program,  and uses these two numeric\r
+parameters to set  the size of the cursor.\r
+\r
+\r
+     #include <nandef.h>            /* Clipper header files */\r
+     #include <extend.h>\r
+     #include <dos.h>                   /* Header file to define REGS */\r
+     \r
+     CLIPPER s_curset()\r
+     {\r
+          /* Demonstration function to set cursor shape */\r
+     \r
+          union REGS inreg,outreg;\r
+     \r
+          inreg.h.ah = 0x01;\r
+          inreg.h.ch = _parni(1);   /* Get integer parameter 1 */\r
+          inreg.h.cl = _parni(2);   /* Get integer parameter 2 */\r
+          int86(0x10,&inreg,&outreg);\r
+          _ret();                        /* Return to Clipper */\r
+     }\r
+\r
+Clipper  provides four more functions for dealing with received\r
+parameters;\r
+\r
+_parclen(int,[int])  which returns the length of a string including\r
+imbedded `\0's,   _parcsiz(int[int])  which returns the length of a\r
+character  string passed by reference from Clipper, _parinfa(int,[int])\r
+which returns the type of  a  specified  array  element  or the length of\r
+an  array,   and  finally _parinfo(int) whic returns the type of a\r
+parameter.\r
+\r
+The following example function uses _parinfa()  to determine both the\r
+length of an array passed from a Clipper program,  and the type of each\r
+element  in the array.  The function then returns to Clipper an integer\r
+representing the number of defined elements in the array.\r
+\r
+\r
+\r
+     #include <nandef.h>\r
+     #include <extend.h>\r
+     \r
+     CLIPPER s_alen()\r
+     {\r
+          int total;\r
+          int n;\r
+          int defined;\r
+          int type;\r
+     \r
+          /* Return the number of defined elements in an array */\r
+          /* From Clipper use defined = s_alen(arr) */\r
+     \r
+          total = _parinfa(1,0); /* Get declared number of elements in\r
+     array */\r
+     \r
+          defined = 0;\r
+     \r
+          for (n = 1; n <= total; n++){\r
+               type = _parinfa(1,n);   /* Get array parameter type */\r
+               if (type)\r
+                    defined++;\r
+          }\r
+          _retni(defined);              /* Return an integer to Clipper\r
+     */\r
+     }\r
+     \r
+\r
+This  function  goes  one step further to return the  mean  average  of\r
+all numeric  values  in an array.  Notice the use of _parnd()  to\r
+retrieve  the numeric  values as doubles.  You may find that because of\r
+the floating point arithmetic  in  this  function  that it will  only\r
+work  if  compiled  with Microsoft C.\r
+\r
+\r
+     #include <nandef.h>\r
+     #include <extend.h>\r
+     \r
+     CLIPPER s_aave()\r
+     {\r
+          int total;\r
+          int defined;\r
+          int n;\r
+          int type;\r
+          double sum;\r
+     \r
+          /* Return the mean average value of numbers in array */\r
+          /* From Clipper use mean = s_aave(arr)\r
+     \r
+     \r
+          total = _parinfa(1,0);               /* Get declared number of\r
+     elements */\r
+     \r
+          defined = 0;\r
+     \r
+          for (n = 1; n <= total; n++){    /* Determine number of defined\r
+     */\r
+               type = _parinfa(1,n);            /* elements */\r
+               if (type == 2)\r
+                    defined++;\r
+          }\r
+     \r
+          sum = 0;\r
+     \r
+          for (n = 1; n <= total; n++){\r
+               type = _parinfa(1,n);\r
+               if (type == 2)                  /* Only sum numeric values\r
+     */\r
+                    sum += _parnd(1,n);\r
+          }\r
+          _retnd(sum / defined);               /* Return a double to\r
+     Clipper */\r
+     }\r
+     \r
+     \r
+\r
+Returning Values\r
+\r
+The  Clipper  manual  lists seven functions for returning  from  a\r
+function written in another language. These return functions for C are as\r
+follows;\r
+\r
+  character        _retc(char *)\r
+  date        _retds(char *)\r
+  logical     _retl(int)\r
+  numeric (int)    _retni(int)\r
+  numeric (long)   _retnl(long)\r
+  numeric  (double)     _retnd(double)\r
+  nothing     _ret(void)\r
+\r
+Omitted  from  the  Clipper manual is the information that  you  may\r
+return different types of value back from a function! For example,  you\r
+may wish to return a character string under normal circumstances,  fine\r
+use _retc().  On error occurences however you can return a logical using\r
+_retl(). The Clipper program  will  assign the received value to the\r
+receiving  variable  in  the correct manner.\r
+\r
+The following simple C function returns a random number.  Notice the use\r
+of integers  which  limits  the range of the function to +-32767.   For\r
+larger values you should use longs instead of integers.\r
+\r
+\r
+     #include <nandef.h>\r
+     #include <extend.h>\r
+     #include <dos.h>\r
+     \r
+     CLIPPER s_random()\r
+     {\r
+          /* Returns a random number between 0 and param1 - 1 */\r
+          /* From Clipper use x = s_random(param1) */\r
+     \r
+          int param1;\r
+          int x;\r
+     \r
+          param1 = _parni(1);\r
+     \r
+          x = rand() % param1;\r
+          _retni(x);\r
+     }\r
+     \r
+\r
+This function receives a string from Clipper,  and passes back an upper\r
+case copy of the string,  leaving the original unchanged.  The maximum\r
+length  of the  string  which can be processed is determined by the size\r
+of  target[], here set to 5000 characters.\r
+\r
+\r
+     #include <nandef.h>\r
+     #include <extend.h>\r
+     \r
+     CLIPPER s_upper()\r
+     {\r
+          /* Returns an upper case copy of string */\r
+          /* From Clipper use ? s_upper("this is a string")\r
+     \r
+          char *p;\r
+          char *q;\r
+          char *string;\r
+          char target[5000];\r
+          int n;\r
+     \r
+          string = _parc(1);\r
+     \r
+          p = string;\r
+          q = target;\r
+     \r
+          while(*string){\r
+               *q++ = toupper(*string);\r
+               string++;\r
+          }\r
+          *q = '\0';\r
+          string = p;\r
+          _retc(target);\r
+     }\r
+     \r
+     \r
+This  function  may be used to change the current DOS directory.  If  it\r
+is successful  it  returns .T.  to the calling Clipper program,\r
+otherwise  it returns .F.\r
+\r
+     #include <nandef.h>\r
+     #include <extend.h>\r
+     #include <dos.h>\r
+     \r
+     CLIPPER s_chdir()\r
+     {\r
+          /* Attempts to change the current DOS directory */\r
+          /* From Clipper use result = s_chdir(path) */\r
+     \r
+          union REGS inreg,outreg;\r
+          struct SREGS segreg;\r
+     \r
+          char *path;\r
+          int x;\r
+     \r
+          path = _parc(1);              /* Retrieve string from Clipper\r
+     */\r
+     \r
+          inreg.h.ah = 0x3b;\r
+          segreg.ds = FP_SEG(path);\r
+          inreg.x.dx = FP_OFF(path);\r
+          intdosx(&inreg,&outreg,&segreg);\r
+     \r
+          x = outreg.x.ax;\r
+     \r
+          if (x == 3)\r
+               _retl(0);    /* Return logical .F. back to Clipper */\r
+          else\r
+               _retl(1);    /* Return logical .T. back to Clipper */\r
+     }\r
+     \r
+\r
+\r
+Avoiding Unresolved Externals\r
+\r
+As we have already seen, a common problem plaguing the programmer\r
+interfacing C functions with Clipper programs is Unresolved Externals.\r
+\r
+The  following  example  C function called s_print()   will  not  link\r
+with Clipper.\r
+\r
+     #include <nandef.h>\r
+     #include <extend.h>\r
+     #include <stdio.h>\r
+     \r
+     CLIPPER s_print()\r
+     {\r
+          char *x;\r
+     \r
+          x = _parc(1);\r
+     \r
+          printf("\nI received %s from Clipper.\n",x);\r
+     \r
+          _ret();\r
+     }\r
+     \r
+\r
+The linker gives you the following reply;\r
+\r
+     Microsoft (R) Overlay Linker  Version 3.65\r
+     Copyright (C) Microsoft Corp 1983-1988.  All rights reserved.\r
+     \r
+     M:SLIB.LIB(IOERROR) : error L2025: __doserrno : symbol defined more\r
+     than once\r
+      pos: 16C6F Record type: 53C6\r
+     \r
+     LINK : error L2029: Unresolved externals:\r
+     \r
+     \r
+\r
+     __RealCvtVector in file(s):\r
+      M:SLIB.LIB(REALCVT)\r
+     _abort in file(s):\r
+      M:SLIB.LIB(CVTFAK)\r
+     \r
+     There were 3 errors detected\r
+     \r
+\r
+The error L2025 `symbol defined more than once' can in this case be\r
+ignored.  However,   the unresolved externals `RealCvtVector'  and\r
+`abort'  cannot  be ignored.  These two functions are referenced by the\r
+function printf()  which has  been  included in the C function.  The\r
+answer is to use as few  of  the compiler's  run time library functions\r
+as possible,  use ROM  calls  instead with INT86() and INTDOSX() etc.\r
+\r
+\r
+Adding High Resolution Graphics To Clipper With C\r
+\r
+The most annoying omission from Clipper, in my opinion, is the lack of\r
+high resolution graphics facilities. The following functions, written in\r
+Turbo C, provide high resolution graphics to Clipper.\r
+\r
+First we require a means to change the video display mode to a high\r
+resolution graphics mode, and back to text mode. The IBM PC BIOS provides\r
+the means for this and can be called from C as follows;\r
+\r
+\r
+\r
+\r
+     /*         Servile Software Library For Clipper              */\r
+     \r
+     #include <nandef.h>\r
+     #include <extend.h>\r
+     #include <dos.h>\r
+     \r
+     CLIPPER s_smode()\r
+     {\r
+          /* Set Video Mode */\r
+          /* From Clipper use s_smode(mode) */\r
+     \r
+          union REGS inreg,outreg;\r
+     \r
+          inreg.h.al = _parni(1);\r
+          inreg.h.ah = 0x00;\r
+          int86 (0x10, &inreg, &outreg);\r
+     \r
+     \r
+     /*  1 40x25 colour text\r
+          2 40x25 bw text\r
+          3 80x25 colour text\r
+          4 320x200 4 colour graphics\r
+          5 320x200 4 colour graphics colour burst off\r
+          6 640x200 2 colour graphics\r
+          etc\r
+     */\r
+          _ret();\r
+     }\r
+\r
+Having set the computer into graphics mode, how about setting pixels to a\r
+specified colour?\r
+\r
+     \r
+     /*         Servile Software Library For Clipper              */\r
+     \r
+     #include <nandef.h>\r
+     #include <extend.h>\r
+     #include <dos.h>\r
+     \r
+     CLIPPER s_plot()\r
+     {\r
+     \r
+          union REGS inreg,outreg;\r
+     \r
+          /* Sets a pixel at the specified coordinates to the specified\r
+     colour. */\r
+     \r
+          inreg.h.bh = 0x00;\r
+          inreg.x.cx = _parni(1);\r
+          inreg.x.dx = _parni(2);\r
+          inreg.h.al = _parni(3);\r
+          inreg.h.ah = 0x0C;\r
+          int86(0x10, &inreg, &outreg);\r
+     }\r
+     \r
+Line drawing and circles are handled by these two functions;\r
+\r
+     \r
+     /*         Servile Software Library For Clipper              */\r
+     \r
+     #include <nandef.h>\r
+     #include <extend.h>\r
+     #include <dos.h>\r
+     \r
+     CLIPPER s_line()\r
+     {\r
+     \r
+          union REGS inreg,outreg;\r
+     \r
+          /* Draws a straight line from (a,b) to (c,d) in colour col */\r
+     \r
+          int a;\r
+          int b;\r
+          int c;\r
+          int d;\r
+          int col;\r
+          int u;\r
+          int v;\r
+          int d1x;\r
+          int d1y;\r
+          int d2x;\r
+          int d2y;\r
+          int m;\r
+          int n;\r
+          int s;\r
+          int i;\r
+     \r
+          a = _parni(1);\r
+          b = _parni(2);\r
+          c = _parni(3);\r
+          d = _parni(4);\r
+          col = _parni(5);\r
+     \r
+          u = c - a;\r
+          v = d - b;\r
+          if (u == 0)\r
+          {\r
+               d1x = 0;\r
+               m = 0;\r
+          }\r
+          else\r
+          {\r
+               m = abs(u);\r
+               if (u < 0)\r
+                    d1x = -1;\r
+               else\r
+                    if (u > 0)\r
+                         d1x = 1;\r
+          }\r
+          if ( v == 0)\r
+          {\r
+               d1y = 0;\r
+               n = 0;\r
+          }\r
+          else\r
+          {\r
+               n = abs(v);\r
+               if (v < 0)\r
+                    d1y = -1;\r
+               else\r
+                    if (v > 0)\r
+                         d1y = 1;\r
+          }\r
+          if (m > n)\r
+          {\r
+               d2x = d1x;\r
+               d2y = 0;\r
+          }\r
+          else\r
+          {\r
+               d2x = 0;\r
+               d2y = d1y;\r
+               m = n;\r
+               n = abs(u);\r
+          }\r
+          s = (m / 2);\r
+     \r
+          inreg.h.al = (unsigned char)col;\r
+          inreg.h.bh = 0x00;\r
+          inreg.h.ah = 0x0C;\r
+          for (i = 0; i <= m; i++)\r
+          {\r
+               inreg.x.cx = (unsigned int)(a);\r
+               inreg.x.dx = (unsigned int)(b);\r
+               int86(0x10, &inreg, &outreg);\r
+               s += n;\r
+               if (s >= m)\r
+               {\r
+                    s -= m;\r
+                    a += d1x;\r
+                    b += d1y;\r
+               }\r
+               else\r
+               {\r
+                    a += d2x;\r
+                    b += d2y;\r
+               }\r
+          }\r
+     }\r
+     \r
+     \r
+\r
+This circle drawing function uses in-line assembler to speed up the\r
+drawing process. It can easily be replaced with inreg and outreg\r
+parameters as in the other functions, or the other functions can be\r
+changed to in-line assembler.  Both methods are shown to illustrate\r
+different ways of achieving the same result.\r
+\r
+\r
+     /*         Servile Software Library For Clipper              */\r
+     \r
+     #include <nandef.h>\r
+     #include <extend.h>\r
+     #include <dos.h>\r
+     \r
+     \r
+     void plot(int x, int y, unsigned char colour)\r
+     {\r
+          asm mov al , colour;\r
+          asm mov bh , 00;\r
+          asm mov cx , x;\r
+          asm mov dx , y;\r
+          asm mov ah , 0Ch;\r
+          asm int 10h;\r
+     }\r
+     \r
+     int getmode()\r
+     {\r
+          /* Returns current video mode  and number of columns in ncols\r
+     */\r
+     \r
+          asm mov ah , 0Fh;\r
+          asm int 10h;\r
+          return(_AL);\r
+     }\r
+     \r
+     \r
+     CLIPPER s_circle()\r
+     {\r
+          int x_centre;\r
+          int y_centre;\r
+          int radius;\r
+          int colour;\r
+          int x,y,delta;\r
+          int startx,endx,x1,starty,endy,y1;\r
+          int asp_ratio;\r
+     \r
+          x_centre = _parni(1);\r
+          y_centre = _parni(2);\r
+          radius = _parni(3);\r
+          colour = _parni(4);\r
+     \r
+     \r
+     \r
+     \r
+          if (getmode() == 6)\r
+               asp_ratio = 22;\r
+          else\r
+               asp_ratio = 13;\r
+     \r
+          y = radius;\r
+          delta = 3 - 2 * radius;\r
+     \r
+          for(x = 0; x < y; )\r
+          {\r
+               starty = y * asp_ratio / 10;\r
+               endy = (y + 1) * asp_ratio / 10;\r
+               startx = x * asp_ratio / 10;\r
+               endx = (x + 1) * asp_ratio / 10;\r
+     \r
+               for(x1 = startx; x1 < endx; ++x1)\r
+               {\r
+                    plot(x1+x_centre,y+y_centre,colour);\r
+                    plot(x1+x_centre,y_centre - y,colour);\r
+                    plot(x_centre - x1,y_centre - y,colour);\r
+                    plot(x_centre - x1,y + y_centre,colour);\r
+               }\r
+     \r
+               for(y1 = starty; y1 < endy; ++y1)\r
+               {\r
+                    plot(y1+x_centre,x+y_centre,colour);\r
+                    plot(y1+x_centre,y_centre - x,colour);\r
+                    plot(x_centre - y1,y_centre - x,colour);\r
+                    plot(x_centre - y1,x + y_centre,colour);\r
+               }\r
+     \r
+               if (delta < 0)\r
+                    delta += 4 * x + 6;\r
+               else\r
+               {\r
+                    delta += 4*(x-y)+10;\r
+                    y--;\r
+               }\r
+               x++;\r
+          }\r
+     \r
+     \r
+     \r
+     \r
+          if(y)\r
+          {\r
+               starty = y * asp_ratio / 10;\r
+               endy = (y + 1) * asp_ratio / 10;\r
+               startx = x * asp_ratio / 10;\r
+               endx = (x + 1) * asp_ratio / 10;\r
+               for(x1 = startx; x1 < endx; ++x1)\r
+               {\r
+                    plot(x1+x_centre,y+y_centre,colour);\r
+                    plot(x1+x_centre,y_centre - y,colour);\r
+                    plot(x_centre - x1,y_centre - y,colour);\r
+                    plot(x_centre - x1,y + y_centre,colour);\r
+               }\r
+     \r
+               for(y1 = starty; y1 < endy; ++y1)\r
+               {\r
+                    plot(y1+x_centre,x+y_centre,colour);\r
+                    plot(y1+x_centre,y_centre - x,colour);\r
+                    plot(x_centre - y1,y_centre - x,colour);\r
+                    plot(x_centre - y1,x + y_centre,colour);\r
+               }\r
+          }\r
+     }\r
+     \r
+\r
+The Clipper facilities for displaying text on the screen, @....SAY and ?\r
+do not work when the monitor is in graphics mode. You then need the\r
+following function to allow text to be displayed in a graphics mode;\r
+\r
+     \r
+     /*         Servile Software Library For Clipper              */\r
+     \r
+     #include <nandef.h>\r
+     #include <extend.h>\r
+     #include <dos.h>\r
+     \r
+     int sgetmode(int *ncols)\r
+     {\r
+          /* Returns current video mode  and number of columns in ncols\r
+     */\r
+     \r
+          union REGS inreg,outreg;\r
+     \r
+          inreg.h.ah = 0x0F;\r
+          int86(0x10, &inreg, &outreg);\r
+          *ncols = outreg.h.ah;\r
+          return(outreg.h.al);\r
+     }\r
+     \r
+     void at(int row, int col)\r
+     {\r
+          asm mov bh , 0;\r
+          asm mov dh , row;\r
+          asm mov dl , col;\r
+          asm mov ah , 02h;\r
+          asm int 10h;\r
+     }\r
+     \r
+     \r
+     \r
+     \r
+     CLIPPER s_say()\r
+     {\r
+          char *output;\r
+          int p = 0;\r
+          unsigned char page;\r
+          unsigned char text;\r
+          int n;\r
+          int r;\r
+          int c;\r
+          int attribute;\r
+     \r
+          output = _parc(1);\r
+          r = _parni(2);\r
+          c = _parni(3);\r
+          attribute = _parni(4);\r
+     \r
+          asm mov ah , 0Fh;\r
+          asm int 10h;\r
+          asm mov page, bh;\r
+     \r
+          sgetmode(&n);\r
+     \r
+          at(r,c);\r
+     \r
+          while (output[p])\r
+          {\r
+               text = output[p++];\r
+               asm mov bh , page;\r
+               asm mov bl , attribute;\r
+               asm mov cx , 01h;\r
+               asm mov ah , 09h;\r
+               asm mov al , text;\r
+               asm int 10h;\r
+               c++;\r
+               if (c < (n-1))\r
+                    at( r, c);\r
+               else\r
+               {\r
+                    c = 0;\r
+                    at(++r,0);\r
+               }\r
+          }\r
+     }\r
+     \r
+\r
+\r
+\r
+When drawing graphs, it is often required to fill in areas of the graph\r
+in different patterns. This is a graphics function to fill boundered\r
+shapes with a specified hatching pattern providing a means to achieve\r
+more usable graphs;\r
+\r
+\r
+     \r
+     /*         Servile Software Library For Clipper              */\r
+     \r
+     #include <nandef.h>\r
+     #include <extend.h>\r
+     #include <dos.h>\r
+     \r
+     int pixset(int x, int y)\r
+     {\r
+          /* Returns the colour of the specified pixel */\r
+     \r
+          asm mov cx ,x;\r
+          asm mov dx ,y;\r
+          asm mov ah ,0Dh;\r
+          asm int 10h;\r
+          return(_AL);\r
+     }\r
+     \r
+     CLIPPER s_fill()\r
+     {\r
+          /* Fill a boundered shape using a hatch pattern */\r
+     \r
+          int mode;\r
+          int xa;\r
+          int ya;\r
+          int bn;\r
+          int byn;\r
+          int x;\r
+          int y;\r
+          int col;\r
+          int pattern;\r
+          int maxx;\r
+          int maxy;\r
+          int hatch[10][8] = { 255,255,255,255,255,255,255,255,\r
+                                   128,64,32,16,8,4,2,1,\r
+                                   1,2,4,8,16,32,64,128,\r
+                                   1,2,4,8,8,4,2,1,\r
+                                   238,238,238,238,238,238,238,238,\r
+                                   170,85,170,85,170,85,170,85,\r
+                                   192,96,48,24,12,6,3,1,\r
+                                   62,62,62,0,227,227,227,0,\r
+                                   129,66,36,24,24,36,66,129,\r
+                                   146,36,146,36,146,36,146,36};\r
+     \r
+          /* Patterns for fill, each integer describes a row of dots */\r
+     \r
+     \r
+     \r
+     \r
+          x = _parni(1);\r
+          y = _parni(2);\r
+          col = _parni(3);\r
+          pattern = _parni(4);\r
+     \r
+          mode = getmode();\r
+     \r
+          switch(mode)\r
+          {\r
+               case 0:\r
+               case 1:\r
+               case 2:\r
+               case 3: break;\r
+               case 4:\r
+               case 9:\r
+               case 13:\r
+               case 19:\r
+               case 5: maxx = 320;\r
+                         maxy = 200;\r
+                         break;\r
+               case 14:\r
+               case 10:\r
+               case 6: maxx = 640;\r
+                         maxy = 200;\r
+                         break;\r
+               case 7: maxx = 720;\r
+                         maxy = 400;\r
+                         break;\r
+               case 8: maxx = 160;\r
+                         maxy = 200;\r
+                         break;\r
+               case 15:\r
+               case 16: maxx = 640;\r
+                          maxy = 350;\r
+                          break;\r
+               case 17:\r
+               case 18: maxx = 640;\r
+                          maxy = 480;\r
+                          break;\r
+     \r
+          }\r
+     \r
+          xa = x;\r
+          ya = y;  /* Save Origin */\r
+     \r
+          if(pixset(x,y))\r
+               return;\r
+     \r
+          bn = 1;\r
+          byn = 0;\r
+     \r
+     \r
+     \r
+     \r
+          do\r
+          {\r
+               if (hatch[pattern][byn] != 0)\r
+               {  /* If blank ignore */\r
+                    do\r
+                    {\r
+                         if ((bn & hatch[pattern][byn]) == bn)\r
+                         {\r
+                              asm mov al , col;\r
+                              asm mov bh , 00;\r
+                              asm mov cx , x;\r
+                              asm mov dx , y;\r
+                              asm mov ah , 0Ch;\r
+                              asm int 10h;\r
+                         }\r
+                         x--;\r
+                         bn <<= 1;\r
+                         if (bn > 128)\r
+                              bn = 1;\r
+                    }\r
+                    while(!pixset(x,y) && (x > -1));\r
+     \r
+                    x = xa + 1;\r
+                    bn = 128;\r
+     \r
+                    do\r
+                    {\r
+                         if ((bn & hatch[pattern][byn]) == bn)\r
+                         {\r
+                              asm mov al , col;\r
+                              asm mov bh , 00;\r
+                              asm mov cx , x;\r
+                              asm mov dx , y;\r
+                              asm mov ah , 0Ch;\r
+                              asm int 10h;\r
+                         }\r
+                         x++;\r
+                         bn >>=1;\r
+                         if (bn <1)\r
+                              bn = 128;\r
+                    }\r
+                    while((!pixset(x,y)) && (x <= maxx));\r
+               }\r
+               x = xa;\r
+               y--;\r
+               bn = 1;\r
+               byn++;\r
+               if (byn > 7)\r
+                    byn = 0;\r
+     \r
+     \r
+     \r
+     \r
+          }\r
+          while(!pixset(x,y) && ( y > -1));\r
+     \r
+          /* Now travel downwards */\r
+     \r
+          y = ya + 1;\r
+     \r
+          byn = 7;\r
+          bn = 1;\r
+          do\r
+          {\r
+               /* Travel left */\r
+               if (hatch[pattern][byn] !=0)\r
+               {\r
+                    do\r
+                    {\r
+                         if ((bn & hatch[pattern][byn]) == bn)\r
+                         {\r
+                              asm mov al , col;\r
+                              asm mov bh , 00;\r
+                              asm mov cx , x;\r
+                              asm mov dx , y;\r
+                              asm mov ah , 0Ch;\r
+                              asm int 10h;\r
+                         }\r
+                         x--;\r
+                         bn <<= 1;\r
+                         if (bn > 128)\r
+                              bn = 1;\r
+                    }\r
+                    while(!pixset(x,y) && (x > -1));\r
+     \r
+                    /* Back to x origin */\r
+                    x = xa + 1 ;\r
+                    bn = 128;\r
+     \r
+                    /* Travel right */\r
+                    do\r
+                    {\r
+                         if ((bn & hatch[pattern][byn]) == bn)\r
+                         {\r
+                              asm mov al , col;\r
+                              asm mov bh , 00;\r
+                              asm mov cx , x;\r
+                              asm mov dx , y;\r
+                              asm mov ah , 0Ch;\r
+                              asm int 10h;\r
+                         }\r
+                         x++;\r
+                         bn >>=1;\r
+     \r
+     \r
+     \r
+     \r
+                         if (bn <1)\r
+                              bn = 128;\r
+                    }\r
+                    while((!pixset(x,y)) && (x <= maxx));\r
+               }\r
+               x = xa;\r
+               bn = 1;\r
+               y++;\r
+               byn--;\r
+               if (byn < 0)\r
+                    byn = 7;\r
+          }\r
+          while((!pixset(x,y)) && (y <= maxy));\r
+     }\r
+     \r
+     \r
+     \r
+                                    \r
+                       SPELL - AN EXAMPLE PROGRAM\r
+\r
+It has been said that example programs provide a good way of learning a\r
+new computer language. On that basis the following simple program is\r
+offered as an example of making use of the dynamic memory allocation\r
+provided by DOS.\r
+\r
+This is a spell checker for ASCII (text) documents, written in, and\r
+making use of Borland's Turbo C text graphics facilities for displaying\r
+windows of text.\r
+\r
+\r
+     /* Spell checker for ascii documents */\r
+     /* Compile with -mc (compact memory model) and unsigned characters\r
+     */\r
+     \r
+     \r
+     #include <stdio.h>\r
+     #include <conio.h>\r
+     #include <fcntl.h>\r
+     #include <io.h>\r
+     #include <dos.h>\r
+     #include <string.h>\r
+     #include <alloc.h>\r
+     #include <ctype.h>\r
+     #include <stdlib.h>\r
+     #include <bios.h>\r
+     #include <dir.h>\r
+     #include <stat.h>\r
+     \r
+     #define  ON           0x06\r
+     #define  OFF         0x20\r
+     #define  MAXIMUM     15000\r
+     #define  WORDLEN     20\r
+     #define  LEFTMARGIN  1\r
+     \r
+     union REGS inreg,outreg;\r
+     \r
+     char *dicname;\r
+     char *dic[MAXIMUM];      /* Array of text lines */\r
+     char word[31];\r
+     char comp[31];\r
+     char fname[160];\r
+     int lastelem;\r
+     char changed;\r
+     char *ignore[100];\r
+     int lastign;\r
+     int insert;\r
+     int n;\r
+     int bp;\r
+     int mp;\r
+     int tp;\r
+     int result;\r
+     \r
+     \r
+     \r
+     \r
+     char *text;\r
+     char *textsav;\r
+     \r
+     void AT(int, int);\r
+     void BANNER(void);\r
+     int COMPARE(void);\r
+     void CORRECT(void);\r
+     void FATAL(char *);\r
+     void FILERR(char *);\r
+     void GETDIC(void);\r
+     void IGNORE(void);\r
+     void INSERT(void);\r
+     int MATCHSTR(char *, char *);\r
+     void SPELL(void);\r
+     void UPDATE(void);\r
+     \r
+     void CURSOR(char status)\r
+     {\r
+          /* Toggle cursor display on and off */\r
+     \r
+          union REGS inreg,outreg;\r
+     \r
+          inreg.h.ah = 1;\r
+          inreg.h.ch = (unsigned char)status;\r
+          inreg.h.cl = 7;\r
+          int86(0x10,&inreg,&outreg);\r
+     }\r
+     \r
+     void DISPLAY(char *text)\r
+     {\r
+          /* Display 'text' expanding tabs and newline characters */\r
+     \r
+          while(*text)\r
+          {\r
+               switch(*text)\r
+               {\r
+                    case '\n':  cputs("\r\n");\r
+                                   break;\r
+                    case '\t':  cputs("      ");\r
+                                   break;\r
+                    default:  putch(*text);\r
+               }\r
+               text++;\r
+          }\r
+     }\r
+     \r
+     \r
+     \r
+     \r
+     void GETDIC()\r
+     {\r
+          /* Read dictionary into memory */\r
+     \r
+          FILE *fp;\r
+          char *p;\r
+          int poscr;\r
+          int handle;\r
+     \r
+          window(1,22,80,24);\r
+          clrscr();\r
+          gotoxy(28,2);\r
+          cprintf("Reading Dictionary....");\r
+     \r
+          changed = 0;\r
+          lastelem = 0;\r
+     \r
+          dicname = searchpath("spell.dic");\r
+          handle = open(dicname,O_RDWR);\r
+          if (handle < 0)\r
+               FILERR("spell.dic");\r
+     \r
+          fp = fdopen(handle,"r");\r
+          if (fp == NULL)\r
+               FILERR("spell.dic");\r
+     \r
+          do\r
+          {\r
+               dic[lastelem] = calloc(WORDLEN,1);\r
+               if (dic[lastelem])\r
+               {\r
+                    p = fgets(dic[lastelem],79,fp);\r
+                    /* Remove carriage return from end of text line */\r
+                    poscr = (int)strlen(dic[lastelem]) - 1;\r
+                    if (dic[lastelem][poscr] == '\n')\r
+                         dic[lastelem][poscr] = 0;\r
+               }\r
+               else\r
+                    FATAL("Unable To Allocate Memory");\r
+          }\r
+          while((p != NULL) && (lastelem++ < MAXIMUM));\r
+     \r
+          lastelem--;\r
+     \r
+          fclose(fp);\r
+     }\r
+     \r
+     \r
+     \r
+     \r
+     void UPDATE()\r
+     {\r
+          FILE *fp;\r
+          int n;\r
+     \r
+          if (changed)\r
+          {\r
+               window(1,22,80,24);\r
+               clrscr();\r
+               gotoxy(27,2);\r
+               cprintf("Updating Dictionary....");\r
+     \r
+               fp = fopen(dicname,"w+");\r
+               if (fp == NULL)\r
+                    FILERR("spell.dic");\r
+     \r
+               for(n = 0; n <= lastelem; n++)\r
+                    fprintf(fp,"%s\n",dic[n]);\r
+     \r
+               fclose(fp);\r
+          }\r
+     }\r
+     \r
+     void IGNORE()\r
+     {\r
+          /* Add a word to the ignore table */\r
+     \r
+          if (lastign < 100)\r
+          {\r
+               ignore[lastign] = calloc(strlen(word) + 1,1);\r
+               if (ignore[lastign])\r
+                    strcpy(ignore[lastign++],comp);\r
+               else\r
+               {\r
+                    clrscr();\r
+                    cprintf("No available memory for new words!\r\nPress\r
+     A key....");\r
+                    bioskey(0);\r
+               }\r
+           }\r
+           else\r
+           {\r
+               clrscr();\r
+               cprintf("No available memory for new words!\r\nPress A\r
+     key....");\r
+               bioskey(0);\r
+           }\r
+     }\r
+     \r
+     \r
+     \r
+     \r
+     void FATAL(char *text)\r
+     {\r
+          /* Fatal error drop out */\r
+     \r
+          textcolor(LIGHTGRAY);\r
+          textbackground(BLACK);\r
+          window(1,1,80,25);\r
+          clrscr();\r
+          printf("SERVILE SOFTWARE\n\nSPELL V1.7\nFATAL ERROR:\r
+     %s\n\n",text);\r
+          CURSOR(ON);\r
+          exit(0);\r
+     }\r
+     \r
+     void FILERR(char *fname)\r
+     {\r
+          char text[60];\r
+     \r
+          strcpy(text,"Unable To Access: ");\r
+          strcat(text,fname);\r
+          FATAL(text);\r
+     }\r
+     \r
+     int COMPARE()\r
+     {\r
+          char **p;\r
+     \r
+          /* Check Ignore table */\r
+          for(p = ignore; p <= &ignore[lastign]; p++)\r
+               if (strcmp(comp,*p) == 0)\r
+                    return(1);\r
+     \r
+          /* Binary search of dictionary file */\r
+          bp = 0;\r
+          tp = lastelem;\r
+          mp = (tp + bp) / 2;\r
+     \r
+     \r
+     \r
+     \r
+          while((result = strcmp(dic[mp],comp)) != 0)\r
+          {\r
+               if (mp >= tp)\r
+               {\r
+                    /* Not found! */\r
+                    insert = mp;\r
+                    if (result > 0)\r
+                         insert--;\r
+                    return(0);\r
+               }\r
+               if (result < 0)\r
+                    bp = mp + 1;\r
+               else\r
+                    tp = mp - 1;\r
+     \r
+               mp = (bp + tp) / 2;\r
+          }\r
+          return(1);\r
+     }\r
+     \r
+     void INSERT()\r
+     {\r
+          int n;\r
+     \r
+          changed = 1;\r
+          lastelem++;\r
+          n = lastelem;\r
+     \r
+          dic[n] = calloc(WORDLEN,1);\r
+     \r
+          if (dic[n] == NULL)\r
+          {\r
+               clrscr();\r
+               cprintf("No available memory for new words!\r\nPress A\r
+     key....");\r
+               bioskey(0);\r
+               free(dic[n]);\r
+               lastelem--;\r
+               return;\r
+          }\r
+     \r
+          while(n > (insert + 1))\r
+          {\r
+               strcpy(dic[n],dic[n-1]);\r
+               n--;\r
+          };\r
+     \r
+          strcpy(dic[insert + 1],comp);\r
+     }\r
+     \r
+     \r
+     \r
+     \r
+     void SPELL()\r
+     {\r
+          FILE *target;\r
+          FILE *source;\r
+          char *p;\r
+          char *x;\r
+          char temp[256];\r
+          char dat1[1250];\r
+          char dat2[1250];\r
+          int c;\r
+          int m;\r
+          int found;\r
+          int curpos;\r
+          int key;\r
+          int row;\r
+          int col;\r
+          int srow;\r
+          int scol;\r
+     \r
+          window(1,1,80,20);\r
+          textcolor(BLACK);\r
+          textbackground(WHITE);\r
+     \r
+          /* Open temporary file to take spell checked copy */\r
+          target = fopen("spell.$$$","w+");\r
+     \r
+          source = fopen(fname,"r");\r
+     \r
+          if (source == NULL)\r
+               FILERR(fname);\r
+     \r
+          lastign = 0;\r
+     \r
+          do\r
+          {\r
+               clrscr();\r
+     \r
+               text = dat1;\r
+     \r
+               p = text;\r
+     \r
+               textsav = dat2;\r
+     \r
+               strcpy(text,"");\r
+     \r
+               /* Display read text */\r
+               row = wherey();\r
+               col = wherex();\r
+     \r
+     \r
+     \r
+     \r
+               for(m = 0; m < 15; m++)\r
+               {\r
+                    x = fgets(temp,200,source);\r
+                    if (x)\r
+                    {\r
+                         strcat(text,temp);\r
+                         DISPLAY(temp);\r
+                    }\r
+                    if (wherey() > 18)\r
+                         break;\r
+               }\r
+     \r
+               /* return cursor to start position */\r
+               gotoxy(col,row);\r
+     \r
+               do\r
+               {\r
+                    memset(word,32,30);\r
+                    curpos = 0;\r
+                    do\r
+                    {\r
+                         c = *text++;\r
+                         if ((isalpha(c)) || (c == '-') && (curpos != 0))\r
+                              word[curpos++] = c;\r
+                    }\r
+                    while(((isalpha(c)) || (c == '-') && (curpos != 0))\r
+                            && (curpos < 30));\r
+                    word[curpos] = 0;\r
+                    strcpy(comp,word);\r
+                    strupr(comp);\r
+     \r
+                    if (*comp != 0)\r
+                    {\r
+                         found = COMPARE();\r
+                         if (!found){\r
+                              textbackground(RED);\r
+                              textcolor(WHITE);\r
+                         }\r
+                    }\r
+                    else\r
+                         found = 1;\r
+     \r
+                    srow = wherey();\r
+                    scol = wherex();\r
+     \r
+                    cputs(word);\r
+                    textbackground(WHITE);\r
+                    textcolor(BLACK);\r
+     \r
+     \r
+     \r
+     \r
+     \r
+                    switch(c)\r
+                    {\r
+                         case '\n': cputs("\r\n");\r
+                                      break;\r
+                         case '\t': cputs("       ");\r
+                                      break;\r
+                         default: putch(c);\r
+                    }\r
+     \r
+                    row = wherey();\r
+                    col = wherex();\r
+     \r
+                    if (!found)\r
+                    {\r
+                         window(1,22,80,24);\r
+                         clrscr();\r
+                         cputs("Unknown word ");\r
+                         textcolor(BLUE);\r
+                         cprintf("%s ",word);\r
+                         textcolor(BLACK);\r
+                         cputs("[A]dd  [I]gnore  [C]orrect  [S]kip");\r
+                         do\r
+                         {\r
+                              key = toupper(getch());\r
+                              if (key == 27)\r
+                                   key = 'Q';\r
+                         }\r
+                         while(strchr("AICSQ",key) == NULL);\r
+     \r
+                         switch(key)\r
+                         {\r
+                              case 'A':INSERT();\r
+                                         break;\r
+     \r
+                              case 'C':CORRECT();\r
+                                         break;\r
+     \r
+                              case 'I':IGNORE();\r
+                                         break;\r
+                         }\r
+     \r
+     \r
+     \r
+     \r
+                         if (key == 'C')\r
+                         {\r
+                              clrscr();\r
+                              gotoxy(1,1);\r
+                              strcpy(textsav,--text);\r
+                              /* Delete old word */\r
+                              text -= strlen(comp);\r
+                              *text = 0;\r
+                              /* Insert new word */\r
+                              strcat(text,word);\r
+                              /* Append remainder of text */\r
+                              strcat(text,textsav);\r
+                              text += strlen(word);\r
+                              text++;\r
+                              /* Length of text may have changed ! */\r
+                              if (strlen(word) < strlen(comp))\r
+                                   col -= (strlen(comp) - strlen(word));\r
+                              window(1,1,80,20);\r
+                              clrscr();\r
+                              DISPLAY(p);\r
+                         }\r
+                         else\r
+                         {\r
+                              clrscr();\r
+                              gotoxy(29,2);\r
+                              cputs("Checking Spelling....");\r
+                              window(1,1,80,20);\r
+                              gotoxy(scol,srow);\r
+                              cputs(word);\r
+                         }\r
+                         window(1,1,80,20);\r
+                         gotoxy(col,row);\r
+                    }\r
+               }\r
+               while((*text) && (key != 'Q'));\r
+               fprintf(target,"%s",p);\r
+          }\r
+          while((x != NULL) && (key != 'Q'));\r
+     \r
+          window(1,22,80,24);\r
+          clrscr();\r
+          gotoxy(27,2);\r
+          cprintf("Writing Updated File....");\r
+     \r
+     \r
+     \r
+     \r
+          do\r
+          {\r
+               p = fgets(temp,200,source);\r
+               if (p)\r
+                    fprintf(target,"%s",temp);\r
+          }\r
+          while(p);\r
+     \r
+          fclose(target);\r
+          fclose(source);\r
+     \r
+          /* Now transfer spell.$$$ to fname */\r
+          unlink(fname);\r
+          rename("SPELL.$$$",fname);\r
+     }\r
+     \r
+     void CORRECT()\r
+     {\r
+          /* Locate a good match and return word */\r
+     \r
+          char text[51];\r
+          int m;\r
+          int n;\r
+          int key;\r
+     \r
+          window(1,22,80,24);\r
+          clrscr();\r
+          gotoxy(25,2);\r
+          cprintf("Searching For Alternatives....");\r
+     \r
+          /* Remove any pending key strokes from keyboard buffer */\r
+          while(kbhit())\r
+               getch();\r
+     \r
+          for(n = 0; n <= lastelem; n++)\r
+          {\r
+               if (MATCHSTR(dic[n],comp))\r
+               {\r
+                    strcpy(text,dic[n]);\r
+                    if (strlen(word) <= strlen(text))\r
+                    {\r
+                         for (m = 0; m < strlen(word); m++)\r
+                         {\r
+                              if (isupper(word[m]))\r
+                                   text[m] = toupper(text[m]);\r
+                              else\r
+                                   text[m] = tolower(text[m]);\r
+                         }\r
+     \r
+     \r
+     \r
+     \r
+                         for(m = strlen(word); m < strlen(text); m++)\r
+                              if (isupper(word[strlen(word)]))\r
+                                   text[m] = toupper(text[m]);\r
+                              else\r
+                                   text[m] = tolower(text[m]);\r
+                    }\r
+                    else\r
+                    {\r
+                         for (m = 0; m < strlen(text); m++)\r
+                         {\r
+                              if (isupper(word[m]))\r
+                                   text[m] = toupper(text[m]);\r
+                              else\r
+                                   text[m] = tolower(text[m]);\r
+                         }\r
+                    }\r
+                    clrscr();\r
+                    cprintf("Replace ");\r
+                    textcolor(BLUE);\r
+                    cprintf("%s ",word);\r
+                    textcolor(BLACK);\r
+                    cprintf("With ");\r
+                    textcolor(BLUE);\r
+                    cprintf("%s",text);\r
+                    textcolor(BLACK);\r
+                    cprintf(" Yes No Continue");\r
+                    do\r
+                    {\r
+                         key = toupper(getch());\r
+                    }\r
+                    while(strchr("YNC",key) == NULL);\r
+                    if (key == 'Y')\r
+                    {\r
+                         strcpy(word,text);\r
+                         return;\r
+                    }\r
+                    clrscr();\r
+                    gotoxy(25,2);\r
+                    cprintf("Searching For Alternatives....");\r
+     \r
+                    /* Remove any pending key strokes from keyboard\r
+     buffer */\r
+                    while(kbhit())\r
+                         getch();\r
+     \r
+                    if (key == 'C')\r
+                         return;\r
+               }\r
+          }\r
+          clrscr();\r
+          gotoxy(23,2);\r
+     \r
+     \r
+     \r
+     \r
+          cprintf("NO ALTERNATIVES FOUND! (Press a key)");\r
+          bioskey(0);\r
+          return;\r
+     }\r
+     \r
+     \r
+     int MATCHSTR(char *src, char *tgt)\r
+     {\r
+          /* Compare two words and return non zero if they are similar */\r
+     \r
+          int match;\r
+          int result;\r
+          int strsrc;\r
+          int strtgt;\r
+          int longest;\r
+     \r
+          strtgt = strlen(strupr(tgt));\r
+          strsrc = strlen(strupr(src));\r
+     \r
+          longest = max(strtgt,strsrc);\r
+     \r
+          match = 0;\r
+     \r
+          if(strtgt > strsrc)\r
+          {\r
+               for(; *src ; match += (*src++ == *tgt++))\r
+                    ;\r
+          }\r
+          else\r
+          {\r
+               for(; *tgt ; match += (*src++ == *tgt++))\r
+                    ;\r
+          }\r
+     \r
+          result = (match * 100 / longest);\r
+     \r
+          /* result holds percentage similarity */\r
+     \r
+          if (result > 50)\r
+               return(1);\r
+          return(0);\r
+     }\r
+     \r
+     void AT(int row, int col)\r
+     {\r
+          /* Position the text cursor */\r
+          inreg.h.bh = 0;\r
+          inreg.h.dh = row;\r
+          inreg.h.dl = col;\r
+          inreg.h.ah = 0x02;\r
+          int86 (0x10, &inreg, &outreg);\r
+     }\r
+     \r
+     void WRTCHA (unsigned char ch, unsigned char attrib,  int num)\r
+     {\r
+          /* Display a character num times in colour attrib */\r
+          /* via the BIOS */\r
+     \r
+          inreg.h.al = ch;\r
+          inreg.h.bh = 0;\r
+          inreg.h.bl = attrib;\r
+          inreg.x.cx = num;\r
+          inreg.h.ah = 0x09;\r
+          int86 (0x10, &inreg, &outreg);\r
+     }\r
+     \r
+     void SHADE_BLOCK(int left,int top,int right,int bottom)\r
+     {\r
+          int c;\r
+     \r
+          AT(bottom,right);\r
+          WRTCHA(223,56,1);\r
+          AT(top,right);\r
+          WRTCHA('á',7,1);\r
+          for (c = top+1; c < bottom; c++)\r
+          {\r
+               AT(c,right);\r
+               WRTCHA(' ',7,1);\r
+          }\r
+          AT(bottom,left+1);\r
+          WRTCHA('\9a',7,right-left);\r
+     }\r
+     \r
+     \r
+     \r
+     \r
+     void BOX(int l, int t, int r, int b)\r
+     {\r
+          /* Draws a single line box around a described area */\r
+     \r
+          int n;\r
+          char top[81];\r
+          char bottom[81];\r
+          char tolc[5];\r
+          char torc[5];\r
+          char bolc[5];\r
+          char borc[5];\r
+          char hoor[5];\r
+     \r
+          sprintf(tolc,"%c",218);\r
+          sprintf(bolc,"%c",192);\r
+          sprintf(hoor,"%c",196);\r
+          sprintf(torc,"%c",191);\r
+          sprintf(borc,"%c",217);\r
+     \r
+               strcpy(top,tolc);\r
+          strcpy(bottom,bolc);\r
+          for(n = l + 1; n < r; n++)\r
+          {\r
+               strcat(top,hoor);\r
+               strcat(bottom,hoor);\r
+          }\r
+          strcat(top,torc);\r
+          strcat(bottom,borc);\r
+     \r
+          window(1,1,80,25);\r
+               gotoxy(l,t);\r
+          cputs(top);\r
+          for (n = t + 1; n < b; n++)\r
+          {\r
+               gotoxy(l,n);\r
+               putch(179);\r
+               gotoxy(r,n);\r
+               putch(179);\r
+          }\r
+          gotoxy(l,b);\r
+          cputs(bottom);\r
+     }\r
+     \r
+     \r
+     \r
+     \r
+     void BANNER()\r
+     {\r
+          window (2,2,78,4);\r
+          textcolor(BLACK);\r
+          textbackground(GREEN);\r
+          clrscr();\r
+          SHADE_BLOCK(1,1,78,4);\r
+          BOX(2,2,78,4);\r
+          gotoxy(4,3);\r
+          cprintf("Servile Software                SPELL CHECKER V1.7\r
+                     (c)1992");\r
+     }\r
+     \r
+     \r
+     void main(int argc, char *argv[])\r
+     {\r
+          char *p;\r
+          char tmp_name[160];\r
+          char tmp_fname[160];\r
+     \r
+          if (argc != 2)\r
+          {\r
+               puts("\nERROR: Usage is SPELL document");\r
+               exit(1);\r
+          }\r
+          else\r
+               strcpy(fname,argv[1]);\r
+     \r
+          CURSOR(OFF);\r
+     \r
+          GETDIC();\r
+     \r
+          window(1,22,80,24);\r
+          clrscr();\r
+          gotoxy(28,2);\r
+          cprintf("Making Backup File....");\r
+     \r
+          strcpy(tmp_fname,argv[1]);\r
+     \r
+          /* Remove extension from tmp_fname */\r
+          p = strchr(tmp_fname,'.');\r
+          if(p)\r
+               *p = 0;\r
+     \r
+          /* Create backup file name using DOS */\r
+          sprintf(tmp_name,"copy %s %s.!s! > NUL",argv[1],tmp_fname);\r
+     \r
+          system(tmp_name);\r
+     \r
+          window(1,1,80,25);\r
+     \r
+     \r
+     \r
+     \r
+          textcolor(WHITE);\r
+          textbackground(BLACK);\r
+          clrscr();\r
+          gotoxy(29,2);\r
+          cprintf("Checking Spelling....");\r
+     \r
+          SPELL();\r
+     \r
+          UPDATE();\r
+          window(1,1,80,25);\r
+          textcolor(LIGHTGRAY);\r
+          textbackground(BLACK);\r
+          clrscr();\r
+          CURSOR(ON);\r
+     }\r
+     \r
+                                    \r
+                         APPENDIX A - USING LINK\r
+\r
+\r
+\r
+General Syntax:\r
+\r
+     LINK [options] obj[,[exe][,[map][,[lib]]]][;]\r
+\r
+`obj' is a list of object files to be linked. Each obj file name must be\r
+separated by a + or a space. If you do not specify an extension, LINK\r
+will assume .OBJ. `exe' allows you to specify a name for the executable\r
+file. If this file name is ommited, LINK will use the first obj file name\r
+and suffix it with .EXE. `map' is an optional map file name. If you\r
+specify the name `NUL', no map file is produced. `lib' is a list of\r
+library files to link. LINK searches each library file and only links in\r
+modules which are referenced.\r
+\r
+\r
+eg:\r
+\r
+     LINK filea+fileb,myfile,NUL;\r
+\r
+Links .obj files `filea.obj' and `fileb.obj' into .exe file `myfile.exe'\r
+with no map file produced. The ; at the end of the line tells LINK that\r
+there are no more parameters.\r
+\r
+\r
+\r
+Using Overlays\r
+\r
+Overlay .obj modules are specified by encasing the .obj name in\r
+parenthesis in the link line.\r
+\r
+eg:\r
+\r
+     LINK filea + (fileb) + (filec),myfile,NUL;\r
+\r
+Will link filea.obj fileb.obj and filec.obj with modules fileb.obj and\r
+filec.obj as overlay code.\r
+\r
+\r
+Overlay modules must use FAR call/return instructions.\r
+\r
+\r
+Linker Options\r
+\r
+All LINK options commence with a forward slash `/'. Options which accept\r
+a number can accept a decimal number or a hex number prefixed 0X. eg:\r
+0X10 is interpreted as 10h, decimal 16.\r
+\r
+\r
+\r
+Pause during Linking (/PAU)\r
+\r
+     Tells LINK to wait before writing the .exe file to disk. LINK\r
+displays a\r
+     message and waits for you to press enter.\r
+\r
+Display Linker Process Information (/I)\r
+\r
+     Tells LINK to display information about the link process.\r
+\r
+Pack Executable File (/E)\r
+\r
+     Tells LINK to remove sequences of repeated bytes and to optimise the\r
+load-time\r
+     relocation table before creating the executable file. Symbolic debug\r
+     information is stripped out of the file.\r
+\r
+List Public Symbols (/M)\r
+\r
+     Tells LINK to create a list of all public symbols defined in the\r
+     object files\r
+     in the MAP file.\r
+\r
+Include Line Numbers In Map File (/LI)\r
+\r
+     Tells LINK to include line numbers and associated addresses of the\r
+source\r
+     program in the MAP file.\r
+\r
+Preserve Case Sensitivity (/NOI)\r
+\r
+     By default LINK treats uppercase and lowercase letters as the same.\r
+This\r
+     option tells LINK that they are different.\r
+\r
+Ignore Default Libraries (/NOD)\r
+\r
+     Tells LINK not to search any library specified in the object files\r
+to resolve\r
+     external references.\r
+\r
+Controlling Stack Size (/ST:n)\r
+\r
+     Specifies the size of the stack segment where 'n' is the number of\r
+bytes.\r
+\r
+Setting Maximum Allocation Space (/CP:n)\r
+\r
+     Tells LINK to write the parameter 'n' into the exe file header. When\r
+the exe\r
+     file is executed by DOS, 'n' 16 byte paragraphs of memory are\r
+reserved. If 'n'\r
+     is less than the minimum required, it will be set to the minimum.\r
+This option\r
+     is ESSENTIAL to free memory from the program. C programs free memory\r
+     automatically on start-up, assembly language programs which want to\r
+use\r
+     dynamic memory allocation must be linked with this option set to a\r
+minimum.\r
+\r
+Setting Maximum Number Of Segments (/SE:n)\r
+\r
+     Tells LINK how many segments a program is allowed to have. The\r
+default is 128\r
+     but 'n' can be any number between 1 and 3072.\r
+\r
+\r
+Setting Overlay Interrupt (/O:n)\r
+\r
+     Tells LINK which interrupt number will be used for passing control\r
+to\r
+     overlays. The default is 63. Valid values for 'n' are 0 through 255.\r
+\r
+Ordering Segments (/DO)\r
+\r
+     Tells LINK to use DOS segment ordering. This option is also enabled\r
+by the\r
+     MASM directive .DOSSEG.\r
+\r
+Controlling Data Loading (/DS)\r
+\r
+     By default LINK loads all data starting at the low end of the data\r
+segment. At\r
+     run time the DS register is set to the lowest possible address to\r
+allow the\r
+     entire data segment to be used. This option tells LINK to load all\r
+data\r
+     starting at the high end of the data segment.\r
+\r
+Control Exe File Loading (/HI)\r
+\r
+     Tells LINK to place the exe file as high as possible in memory.\r
+\r
+Prepare for Debugging (/CO)\r
+\r
+     Tells LINK to include symbolic debug information for use by\r
+codeview.\r
+\r
+Optimising Far Calls (/F)\r
+\r
+     Tells LINK to translate FAR calls to NEAR calls where possible. This\r
+results\r
+     in faster code.\r
+\r
+Disabling Far Call Optimisation (/NOF)\r
+\r
+     Tells LINK not to translate FAR calls. This option is specified by\r
+default.\r
+\r
+Packing Contiguous Segments (/PAC:n)\r
+\r
+     Tells LINK to group together neighbouring code segments, providing\r
+more\r
+     oportunities for FAR call translation. 'n' specifies the maximum\r
+size of a\r
+     segment. By default 'n' is 65530. This option is only relevant to\r
+obj files\r
+     created using FAR calls.\r
+\r
+Disabling Segment Packing (/NOP)\r
+\r
+     Disables segment packing. This option is specified by default.\r
+\r
+\r
+\r
+Using Response Files\r
+\r
+Linker options and file names may be specified in a response file. Each\r
+file list starting on a new line instead of being separated by a comma.\r
+\r
+eg:\r
+\r
+     filea.obj fileb.obj\r
+     myfile.exe\r
+     NUL\r
+     liba.lib libb.lib\r
+     \r
+A response file is specified to LINK by prefixing the response file name\r
+with '@'.\r
+eg:\r
+\r
+     LINK @response\r
+\r
index a6a4be1..ebc15e3 100644 (file)
--- a/makefile
+++ b/makefile
@@ -1,17 +1,17 @@
-FLAGS=-0 -d2 
-SRC=src\
+FLAGS=-0 -d2 \r
+SRC=src\\r
 SRCLIB=$(SRC)lib\\r
 all: test.exe pcxtest.exe test2.exe scroll.exe\r
 \r
 scroll.exe: scroll.obj modex16.obj dos_kb.obj bitmap.obj\r
-       wcl $(FLAGS) scroll.obj modex16.obj dos_kb.obj bitmap.obj\r
+       wcl $(FLAGS) scroll.obj modex16.obj dos_kb.obj bitmap.obj 16\lib\x\modex.lib\r
 scroll.obj: $(SRC)scroll.c\r
        wcl $(FLAGS) -c $(SRC)scroll.c\r
 test.exe: test.obj modex16.obj bitmap.obj\r
        wcl $(FLAGS) test.obj modex16.obj bitmap.obj\r
        \r
-test2.exe: test2.obj modex16.obj bitmap.obj\r
-       wcl $(FLAGS) test2.obj modex16.obj bitmap.obj\r
+test2.exe: test2.obj modex16.obj bitmap.obj planar.obj\r
+       wcl $(FLAGS) test2.obj modex16.obj bitmap.obj planar.obj\r
        \r
 pcxtest.exe: pcxtest.obj modex16.obj bitmap.obj\r
        wcl $(FLAGS) pcxtest.obj modex16.obj bitmap.obj\r
@@ -33,6 +33,9 @@ dos_kb.obj: $(SRCLIB)dos_kb.h $(SRCLIB)dos_kb.c
 \r
 bitmap.obj: $(SRCLIB)bitmap.h $(SRCLIB)bitmap.c\r
        wcl $(FLAGS) -c $(SRCLIB)bitmap.c\r
+\r
+planar.obj: $(SRCLIB)planar.h $(SRCLIB)planar.c\r
+       wcl $(FLAGS) -c $(SRCLIB)planar.c\r
        \r
 clean: \r
        del *.obj\r
index 3da4c5e..41fd3d9 100644 (file)
Binary files a/pcxtest.exe and b/pcxtest.exe differ
diff --git a/planarNotes.txt b/planarNotes.txt
new file mode 100644 (file)
index 0000000..f929393
--- /dev/null
@@ -0,0 +1,26 @@
+The first case to consider is that where the image width is divisible
+by 4.  This case is shown below:
+
+Planar Format     Normal Raster order
+=============    ===================
+0 0 1 2 3                PI
+  4 5 6 7        00 10 20 30 01 11 21 31 02 12 22 32 03 13 23 33
+  8 9 A B         04 14 24 34 05 15 25 35 06 16 26 36 07 17 27 37
+                         08 18 28 38 09 19 29 39 0A 1A 2A 3A 0B 1B 2B 3B
+1 0 1 2 3
+  4 5 6 7
+  8 9 A B
+
+2 0 1 2 3
+  4 5 6 7
+  8 9 A B
+
+3 0 1 2 3
+  4 5 6 7
+  8 9 A B
+
+Suppose we would like to render the image from (1,0) to (3,1), we
+would render the coordinates:
+
+  10 20 30
+  14 24 34
index 8734952..706aea7 100644 (file)
Binary files a/scroll.exe and b/scroll.exe differ
diff --git a/src/lib/PLANAR.C b/src/lib/PLANAR.C
new file mode 100644 (file)
index 0000000..8b9f004
--- /dev/null
@@ -0,0 +1,78 @@
+/*\r
+ * Implimentation of the planar buffer files.\r
+ */\r
+#include <stdlib.h>\r
+#include "planar.h"\r
+\r
+/* creates a planar buffer from the bitmap data.\r
+   The planar buffer is dynamically allocated, and should\r
+   be destroyed with the planar_buf_free function when no longer\r
+   needed.\r
+ */\r
+planar_buf_t *\r
+planar_buf_from_bitmap(bitmap_t *b) {\r
+    planar_buf_t *p;\r
+    int plane, bi, pi, x, y;\r
+\r
+    /* allocate the buffer */\r
+    p = planar_buf_alloc(b->width, b->height);\r
+\r
+    /* copy the bitmap data into the planar format */\r
+    bi=0;\r
+    pi=0;\r
+    for(y=0; y < b->height; y++) {\r
+       /* start on the first plane */\r
+       plane=0;\r
+       for(x=0; x < b->width; x++) {\r
+           /* copy to each plane */\r
+           p->plane[plane++][pi]=b->data[bi++];\r
+\r
+           /* handle the completion of 4 planes. */\r
+           if(plane==4) {\r
+               plane=0;\r
+               pi++;\r
+           }\r
+       }\r
+\r
+       /* correct for images not divisible by 4 */\r
+       if(plane) pi++;\r
+    }\r
+\r
+    return p;\r
+}\r
+\r
+\r
+/* allocates a planar buffer with specified dimensions */\r
+planar_buf_t *\r
+planar_buf_alloc(word width, word height) {\r
+    planar_buf_t *p;\r
+    int i;\r
+\r
+    /* allocate the structure and populate sizes */\r
+    p=malloc(sizeof(planar_buf_t));\r
+    p->width  = width;\r
+    p->height = height;\r
+    p->pwidth = width / 4 + (width%4 ? 1 : 0);\r
+\r
+    /* allocate the planes */\r
+    for(i=0; i<4; i++) {\r
+       p->plane[i] = malloc(p->height * p->pwidth);\r
+    }\r
+\r
+    return p;\r
+}\r
+\r
+\r
+/* deallocates a planar buffer */\r
+void\r
+planar_buf_free(planar_buf_t *p) {\r
+    int i;\r
+\r
+    /* free the planes */\r
+    for(i=0; i<4; i++) {\r
+       free(p->plane[i]);\r
+    }\r
+\r
+    /* free the structure */\r
+    free(p);\r
+}\r
diff --git a/src/lib/ems.c b/src/lib/ems.c
new file mode 100644 (file)
index 0000000..9883f60
--- /dev/null
@@ -0,0 +1,289 @@
+//------- PRACE S EMS pameti ---------------\r
+#include <stdio.h>\r
+#include <fcntl.h>\r
+#include <io.h>\r
+#include <dos.h>\r
+#include <mem.h>\r
+\r
+typedef struct\r
+  {\r
+  unsigned long length;         /* velikost prenasene pameti      */\r
+  unsigned int sourceH;         /* handle pro zdroj (0=konvencni  */\r
+  unsigned long sourceOff;      /* offset zdroje pameti           */\r
+  unsigned int destH;           /* handle pro terc (0=konvencni   */\r
+  unsigned long destOff;        /* offset terce pameti            */\r
+  } XMOVE;\r
+\r
+int get_emem(void);             // Fce pro zachazeni s EMS\r
+int alloc_emem(int n);          // Alokuje n KB pameti, vraci handle\r
+int dealloc_emem(int h);        // Dealokuje EMS (handle)\r
+int move_emem(XMOVE *p);        // presune blok z/do EMS\r
+int mem_emem(unsigned *total, unsigned *free);\r
+\r
+#define  pagesizeEMS 0x4000       // pamet EMS je ze 16k stranek\r
+\r
+//int   pagesAllocated = 0;\r
+//int   totalPages;\r
+char *EmsFrame;\r
+\r
+\r
+//------ Zda je EMS driver dostupny: ret=  1 - ANO, 0 - NE\r
+int isEMS(void)\r
+{\r
+    int fh;\r
+    union REGS rg;\r
+\r
+    if((fh=open("EMMXXXX0",O_RDONLY,&fh)) == -1) return( 0 );\r
+\r
+    rg.h.ah = 0x44;\r
+    rg.h.al = 0x00;\r
+    rg.x.bx = fh;\r
+    int86(0x21,&rg,&rg);\r
+    close(fh);\r
+    if(rg.x.cflag) return(0);\r
+    if(rg.x.dx & 0x80)\r
+     return( 1 );\r
+    else\r
+     return( 0 );\r
+}\r
+\r
+//----- Zda je EMS HW dostupny ret=  1 - ANO, 0 - NE\r
+int checkEMS(void)\r
+{\r
+    union REGS rg;\r
+\r
+    rg.h.ah = 0x40;\r
+    int86(0x67,&rg,&rg);\r
+    if(rg.h.ah == 0)\r
+     return( 1 );\r
+    else\r
+     return( 0 );\r
+}\r
+\r
+//----- Vraci totalni pocet stranek EMS nebo -1 ----\r
+int coretotalEMS(void)\r
+{\r
+    union REGS rg;\r
+\r
+    rg.h.ah = 0x42;\r
+    int86(0x67,&rg,&rg);\r
+    if(rg.x.cflag) return( -1 );\r
+    //if(!pagesAllocated)\r
+    // { pagesAllocated = 1;\r
+    //   totalPages = rg.x.dx;\r
+    // }\r
+    return(rg.x.bx);\r
+}\r
+\r
+//----- Vraci pocet volnych stranek EMS nebo -1 ----\r
+int coreleftEMS(void)\r
+{\r
+    union REGS rg;\r
+\r
+    //if(pagesAllocated) return(totalPages);\r
+    rg.h.ah = 0x42;\r
+    int86(0x67,&rg,&rg);\r
+    if(rg.x.cflag) return( -1 );\r
+    //if(!pagesAllocated)\r
+    //pagesAllocated = 1;\r
+    //totalPages = rg.x.dx;\r
+    //return(totalPages);\r
+    return(rg.x.dx);\r
+}\r
+\r
+//----- Vraci EMS page frame (pointr na EMS) nebo NULL ----\r
+char *pageframeEMS(void)\r
+{\r
+    union REGS rg;\r
+\r
+    rg.h.ah = 0x41;\r
+    int86(0x67,&rg,&rg);\r
+    if(rg.h.ah != 0)\r
+     return( NULL );\r
+    else\r
+     return((char *)MK_FP(rg.x.bx,0));\r
+}\r
+\r
+//----- Alokuje n stranek - vraci handle na blok stranek nebo 0 ----\r
+unsigned mallocEMS(int n)\r
+{\r
+    union REGS rg;\r
+\r
+    if(n > coreleftEMS() ) return( 0 );\r
+    rg.h.ah = 0x43;\r
+    rg.x.bx = n;\r
+    int86(0x67,&rg,&rg);\r
+    if(rg.h.ah)\r
+     return( 0 );\r
+    else\r
+     return(rg.x.dx);\r
+}\r
+\r
+//----- Dealokuje blok stranek ret = 1-O.K. 0-ERR -----\r
+unsigned freeEMS(unsigned h)\r
+{\r
+    union REGS rg;\r
+    int   i;\r
+\r
+    for(i=0; i<5; i++)\r
+     { rg.h.ah = 0x45;\r
+       rg.x.dx = h;\r
+       int86(0x67,&rg,&rg);\r
+       if(rg.h.ah == 0) break;\r
+     }\r
+    if(rg.h.ah == 0)\r
+     return( 1 );\r
+    else\r
+     return( 0 );\r
+}\r
+\r
+//----- Mapuje logiclou stranku do fyzicke stranky\r
+int mapEMS(unsigned h, int Ppage, int Lpage)\r
+{\r
+    union REGS rg;\r
+\r
+    if(Ppage < 0 || Ppage > 3) return( 0 );\r
+    rg.h.ah = 0x44;\r
+    rg.h.al = Ppage;\r
+    rg.x.bx = Lpage;\r
+    rg.x.dx = h;\r
+    int86(0x67,&rg,&rg);\r
+    if(rg.h.ah != 0)\r
+     return( 0 );\r
+    else\r
+     return( 1 );\r
+}\r
+\r
+// ##### Fce se stejnymi parametry pro EMS jako pro XMS\r
+\r
+//----- Zda je EMS dostupna\r
+int get_emem(void)\r
+{\r
+  int ist;\r
+\r
+  ist = checkEMS();\r
+  if(ist == 1)\r
+  { ist = isEMS();\r
+    if(ist == 1) return( 0x0300 );\r
+  }\r
+  return( -1 );\r
+}\r
+\r
+//----- Allokuje Kb pameti -------\r
+int alloc_emem(int kb)\r
+{\r
+   int Pages,hhh;\r
+\r
+   Pages = kb / 16;\r
+   if((Pages * 16) < kb) Pages++;\r
+\r
+   hhh = mallocEMS(Pages);\r
+   if(hhh == 0)\r
+    return( -1);\r
+   else\r
+    return(hhh);\r
+}\r
+\r
+//----- dealokuje EMS pres handle\r
+int dealloc_emem(int h)\r
+{\r
+   return( freeEMS( h ) );\r
+}\r
+\r
+//----- presune blok pameti\r
+//  unsigned long length;         /* velikost prenasene pameti      */\r
+//  unsigned int  sourceH;        /* handle pro zdroj (0=konvencni  */\r
+//  unsigned long sourceOff;      /* offset zdroje pameti           */\r
+//  unsigned int  destH;          /* handle pro terc (0=konvencni   */\r
+//  unsigned long destOff;        /* offset terce pameti            */\r
+int move_emem(XMOVE *pxm)\r
+{\r
+   unsigned char *SrcBuf,*DstBuf;\r
+   int      ist;\r
+   unsigned int  SrcOff, DstOff, BegPage, BegOff, FreeByte, CopyLen;\r
+\r
+   if(pxm->sourceH == 0 && pxm->destH != 0)    // Buffer->EMS\r
+   {\r
+     SrcBuf  = (unsigned char *)pxm->sourceOff;// buffer\r
+     SrcOff  = 0;\r
+     BegPage = pxm->destOff / pagesizeEMS;     // pocatecni page\r
+     BegOff  = pxm->destOff % pagesizeEMS;     // offset in page\r
+     FreeByte= pagesizeEMS - BegOff;           // volnych B na page\r
+     CopyLen = pxm->length;                    // celkova delka\r
+\r
+     Next_page:\r
+     if(CopyLen > FreeByte)\r
+     {\r
+       ist = mapEMS(pxm->destH, 0, BegPage);\r
+       if(ist==0) return( 0 );\r
+       memcpy(EmsFrame+BegOff, SrcBuf+SrcOff, FreeByte);\r
+\r
+       CopyLen  = CopyLen - FreeByte;\r
+       SrcOff  += FreeByte;\r
+       BegPage++;\r
+       BegOff   = 0;\r
+       FreeByte = pagesizeEMS;\r
+       goto Next_page;\r
+     }\r
+     else\r
+     {\r
+       ist = mapEMS(pxm->destH, 0, BegPage);\r
+       if(ist==0) return( 0 );\r
+       memcpy(EmsFrame+BegOff, SrcBuf+SrcOff, CopyLen);\r
+     }\r
+   }\r
+   else if(pxm->sourceH != 0 && pxm->destH == 0) // EMS->Buffer\r
+   {\r
+     DstBuf  = (unsigned char *)pxm->destOff;// buffer\r
+     DstOff  = 0;\r
+     BegPage = pxm->sourceOff / pagesizeEMS;     // pocatecni page\r
+     BegOff  = pxm->sourceOff % pagesizeEMS;     // offset in page\r
+     FreeByte= pagesizeEMS - BegOff;             // volnych B na page\r
+     CopyLen = pxm->length;                    // celkova delka\r
+\r
+     Next_page2:\r
+     if(CopyLen > FreeByte)\r
+     {\r
+       ist = mapEMS(pxm->sourceH, 0, BegPage);\r
+       if(ist==0) return( 0 );\r
+       memcpy(DstBuf+DstOff, EmsFrame+BegOff, FreeByte);\r
+\r
+       CopyLen  = CopyLen - FreeByte;\r
+       DstOff  += FreeByte;\r
+       BegPage++;\r
+       BegOff   = 0;\r
+       FreeByte = pagesizeEMS;\r
+       goto Next_page2;\r
+     }\r
+     else\r
+     {\r
+       ist = mapEMS(pxm->sourceH, 0, BegPage);\r
+       if(ist==0) return( 0 );\r
+       memcpy(DstBuf+DstOff, EmsFrame+BegOff, CopyLen);\r
+     }\r
+   }\r
+   else   // Error\r
+   { return( 0 );\r
+   }\r
+\r
+   return 1;\r
+}\r
+\r
+// ----- Vrati pocet volnych a max. KB EMS\r
+int mem_emem(unsigned int *total, unsigned int *freeall)\r
+{\r
+ int pom;\r
+\r
+ pom = coretotalEMS();\r
+ if(pom != -1 )\r
+  *total = pom * 16;\r
+ else\r
+  return( 0 );\r
+ pom = coreleftEMS();\r
+ if(pom != -1)\r
+  *freeall = pom * 16;\r
+ else\r
+  return( 0 );\r
+\r
+ return( 1 );\r
+}\r
diff --git a/src/lib/ems1.c b/src/lib/ems1.c
new file mode 100644 (file)
index 0000000..dd9ebe5
--- /dev/null
@@ -0,0 +1,110 @@
+#if !defined(__EMS_H)\r
+#define __EMS_H\r
+\r
+#define EMSPAGESIZE (4096)\r
+#define EMS_ERROR (-1)\r
+struct {\r
+  unsigned long length;\r
+  unsigned char source_type;\r
+  unsigned int source_handle;\r
+  unsigned int source_ofs; \r
+  unsigned int source_seg;\r
+  unsigned char dest_type;\r
+  unsigned int dest_handle;\r
+  unsigned int dest_ofs; \r
+  unsigned int dest_seg;\r
+} myEMSmove;\r
+\r
+\r
+void conv2ems(int PAGE, int HANDLE, char *output) {\r
+      char *x = (char *) &myEMSmove;\r
+      myEMSmove.length = EMSPAGESIZE;\r
+      myEMSmove.source_type = 0;\r
+      myEMSmove.source_handle = 0;\r
+      myEMSmove.source_ofs = FP_OFF(output);\r
+      myEMSmove.source_seg = FP_SEG(output);\r
+      myEMSmove.dest_type = 1;\r
+      myEMSmove.dest_handle = HANDLE;\r
+      myEMSmove.dest_ofs = (PAGE & 3) << 12;\r
+      myEMSmove.dest_seg = PAGE >> 2;\r
+      __asm push ds\r
+      __asm mov       ax, 5700h;        \r
+      __asm lds       si, x\r
+      __asm int       67h \r
+      __asm pop ds\r
+}\r
+\r
+void ems2conv(int PAGE, int HANDLE, char *output) {\r
+      char *x = (char *) &myEMSmove;\r
+      myEMSmove.length = EMSPAGESIZE;\r
+      myEMSmove.source_type = 1;\r
+      myEMSmove.source_handle = HANDLE;\r
+      myEMSmove.source_ofs = (PAGE & 3) << 12;\r
+      myEMSmove.source_seg = PAGE>>2;\r
+      myEMSmove.dest_type = 0;\r
+      myEMSmove.dest_handle = 0;\r
+      myEMSmove.dest_ofs = FP_OFF(output);\r
+      myEMSmove.dest_seg = FP_SEG(output);\r
+      __asm push ds\r
+      __asm mov       ax, 5700h\r
+      __asm lds       si, x\r
+      __asm int       67h \r
+      __asm pop ds\r
+}\r
+\r
+\r
+void freeEMS(int handle) {\r
+  __asm mov       dx, handle;\r
+  __asm mov       ah, 45h;    \r
+  __asm int       67h  \r
+}\r
+\r
+int checkEMS() {\r
+  char *x = "EMMXXXX0";\r
+  __asm {\r
+    mov ax, 3567h\r
+    int 21h\r
+    mov di, 10\r
+    mov si, offset x\r
+    mov cx, 4\r
+    rep cmpsw\r
+    je good \r
+  }\r
+  return -1;\r
+  good:\r
+  __asm {\r
+    sub     ax, ax\r
+    mov     ah, 40h\r
+    int     67h\r
+  }\r
+  return _AX;\r
+}\r
+\r
+int getavailEMS() {\r
+  __asm {\r
+    mov     ah, 42h\r
+    int     67h\r
+    cmp     ah, 00h\r
+    je      getavailEMS_Okay\r
+    mov     bx, -1\r
+  }\r
+  getavailEMS_Okay:\r
+  __asm shl bx, 2\r
+  return _BX;\r
+}\r
+\r
+int allocEMS(int pages) {\r
+  __asm {\r
+    mov     bx, pages\r
+    shr     bx, 2\r
+    mov     ah, 43h\r
+    int     67h\r
+    cmp     ah, 0\r
+    je      allocEMS_Okay\r
+    mov     dx, -1\r
+  }\r
+  allocEMS_Okay:\r
+  return _DX;\r
+}\r
+\r
+#endif\r
index 86b55b8..560ea8a 100644 (file)
@@ -8,6 +8,7 @@
 
 /* Control codes for all keys on the keyboard */
 //here temperarly
+/*
 #define KEY_A          (0x1E)
 #define KEY_B          (0x30)
 #define KEY_C          (0x2E)
 #define KEY_8          (0x09)
 #define KEY_9          (0x0A)
 #define KEY_0          (0x0B)
-#define KEY_DASH               (0x0C)  /* -_ */
-#define KEY_EQUAL              (0x0D)  /* =+ */
-#define KEY_LBRACKET   (0x1A)  /* [{ */
-#define KEY_RBRACKET   (0x1B)  /* ]} */
-#define KEY_SEMICOLON  (0x27)  /* ;: */
-#define KEY_RQUOTE     (0x28)  /* '" */
-#define KEY_LQUOTE     (0x29)  /* `~ */
-#define KEY_PERIOD     (0x33)  /* .> */
-#define KEY_COMMA              (0x34)  /* ,< */
-#define KEY_SLASH              (0x35)  /* /? */
-#define KEY_BACKSLASH  (0x2B)  /* \| */
+#define KEY_DASH               (0x0C)  // -_
+#define KEY_EQUAL              (0x0D)  // =+
+#define KEY_LBRACKET   (0x1A)  // [{
+#define KEY_RBRACKET   (0x1B)  // ]}
+#define KEY_SEMICOLON  (0x27)  // ;:
+#define KEY_RQUOTE     (0x28)  // '"
+#define KEY_LQUOTE     (0x29)  // `~
+#define KEY_PERIOD     (0x33)  // .>
+#define KEY_COMMA              (0x34)  // ,<
+#define KEY_SLASH              (0x35)  // /?
+#define KEY_BACKSLASH  (0x2B)  // \|
 #define KEY_F1         (0x3B)
 #define KEY_F2         (0x3C)
 #define KEY_F3         (0x3D)
@@ -95,7 +96,7 @@
 #define KEY_LWIN               (0x73)
 #define KEY_RWIN               (0x74)
 #define KEY_MENU               (0x75)
-
+*/
 
 //typedef unsigned char byte;
 //typedef unsigned int word;
 
 void wait(clock_t wait);
 
+/* THIS FUNCTION CONVERTS A POINTER TO AN INTEL LONG              */\r
+//int long ptr2long(char *p);
+
 #endif/*_LIBHEAD_H_*/
index 1e1757b..0137b62 100644 (file)
@@ -213,7 +213,7 @@ modexClearRegion(page_t *page, int x, int y, int w, int h, byte  color) {
                JNZ SCAN_START\r
     }\r
 }\r
-
+\r
 \r
 void\r
 modexDrawBmp(page_t *page, int x, int y, bitmap_t *bmp) {\r
@@ -236,7 +236,7 @@ modexDrawBmpRegion(page_t *page, int x, int y,
     word nextBmpRow = (word) bmp->width - width;\r
     word rowCounter;\r
     byte planeCounter = 4;\r
-
+\r
        //code is a bit slow here\r
     __asm {\r
                MOV AX, SCREEN_SEG      ; go to the VGA memory\r
@@ -287,6 +287,12 @@ modexDrawBmpRegion(page_t *page, int x, int y,
 \r
 \r
 void\r
+modexDrawPlanarBuf(page_t *page, int x, int y, planar_buf_t *bmp) {\r
+    /* TODO - adapt from test code */\r
+}\r
+\r
+\r
+void\r
 modexDrawSprite(page_t *page, int x, int y, bitmap_t *bmp) {\r
     /* draw the whole sprite */\r
     modexDrawSpriteRegion(page, x, y, 0, 0, bmp->width, bmp->height, bmp);\r
index 1a5debd..7cb2782 100644 (file)
@@ -6,6 +6,7 @@
 #include <conio.h>\r
 #include "src\lib\types.h"\r
 #include "src\lib\bitmap.h"\r
+#include "src\lib\planar.h"\r
 \r
 /* -========================== Types & Macros ==========================- */\r
 #define PAGE_OFFSET(x,y) (((y)<<6)+((y)<<4)+((x)>>2))\r
@@ -29,11 +30,12 @@ page_t modexNextPage(page_t *p);
 void modexShowPage(page_t *page);\r
 void modexPanPage(page_t *page, int dx, int dy);\r
 void modexSelectPlane(byte plane);\r
-void modexClearRegion(page_t *page, int x, int y, int w, int h, byte color);
+void modexClearRegion(page_t *page, int x, int y, int w, int h, byte color);\r
 void modexDrawBmp(page_t *page, int x, int y, bitmap_t *bmp);\r
 void modexDrawBmpRegion(page_t *page, int x, int y, int rx, int ry, int rw, int rh, bitmap_t *bmp);\r
+void modexDrawPlanarBuf(page_t *page, int x, int y, planar_buf_t *bmp);\r
 void modexDrawSprite(page_t *page, int x, int y, bitmap_t *bmp);\r
-void modexDrawSpriteRegion(page_t *page, int x, int y, int rx, int ry, int rw, int rh, bitmap_t *bmp);
+void modexDrawSpriteRegion(page_t *page, int x, int y, int rx, int ry, int rw, int rh, bitmap_t *bmp);\r
 void modexCopyPageRegion(page_t *dest, page_t *src, word sx, word sy, word dx, word dy, word width, word height);\r
 \r
 /* Palette fade and flash effects */\r
diff --git a/src/lib/planar.h b/src/lib/planar.h
new file mode 100644 (file)
index 0000000..6aa8e26
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Functions and types for a planar image buffer.  
+ * This is meant to be able to load into video memory faster.
+ */
+#include "bitmap.h"
+
+#ifndef PLANAR_H
+#define PLANAR_H
+typedef struct {
+  byte *plane[4];     /* 4 planes of image data */
+  word width;         /* width of the image (spread across 4 planes) */
+  word height;        /* height of the image (spread across 4 planes) */
+  word pwidth;        /* the number of bytes in each plane */
+} planar_buf_t;
+
+
+/* creates a planar buffer from the bitmap data.
+   The planar buffer is dynamically allocated, and should
+   be destroyed with the planar_buf_free function when no longer
+   needed.
+ */
+planar_buf_t *planar_buf_from_bitmap(bitmap_t *b);
+
+
+/* allocates a planar buffer with specified dimensions */
+planar_buf_t *planar_buf_alloc(word width, word height);
+
+
+/* deallocates a planar buffer */
+void planar_buf_free(planar_buf_t *p);
+#endif
diff --git a/src/lib/xmem/xmem.asm b/src/lib/xmem/xmem.asm
new file mode 100644 (file)
index 0000000..3453e08
--- /dev/null
@@ -0,0 +1,753 @@
+;-----------------------------------------------------------------------
+;Can't take credit for the ASM code here, found it on a local BBS.
+;The author has beem lost in the mists of time.
+; 
+;
+.MODEL MEDIUM
+        EXTmemError     EQU     7
+        XMSmemError     EQU     8
+        ShortAdr        EQU     0
+        LongAdr         EQU     1
+procname        MACRO  Pnam
+        PUBLIC  _&Pnam&
+_&Pnam&  PROC    FAR
+ENDM
+endproc         MACRO  Pnam
+_&Pnam&  ENDP
+ENDM
+pwrlolvl_TEXT   SEGMENT WORD PUBLIC 'CODE'
+        ASSUME  CS:pwrlolvl_TEXT, DS:pwrlolvl_TEXT, ES:pwrlolvl_TEXT
+SUBTTL  (Local Procedure) XMS_setup - find a XMS driver.
+PAGE+
+                EVEN
+XMSwordByte     LABEL BYTE
+XMSword         DW      0
+XMSmoveSTRUC    STRUC
+Length          DW      0
+LengthX         DW      0
+SrcHandle       DW      0
+SrcOffset       DW      0
+SrcOffsetX      DW      0
+DestHandle      DW      0
+DestOffset      DW      0
+DestOffsetX     DW      0
+XMSmoveSTRUC    ENDS
+XMSmainGET      XMSmoveSTRUC  <>
+XMSmainPUT      XMSmoveSTRUC  <>
+XMSwordGET      XMSmoveSTRUC  <2,,,,,,OFFSET XMSword>
+XMSwordPUT      XMSmoveSTRUC  <2,,,OFFSET XMSword>
+XMSfunctAdr     DW      0, 0
+; Don't try to call this from your programs
+XMS_setup               PROC NEAR
+        PUSH    DS
+        PUSH    ES
+        PUSH    BX
+        MOV     AX,CS                   ; Set Data segment to the code
+segment.
+        MOV     DS,AX                   ;
+        MOV     [XMSwordGET.DestOffsetX],AX  ; Set up the move data
+structures.
+        MOV     [XMSwordPUT.SrcOffsetX],AX   ;
+        MOV     AX,4300H                ; See if a XMS Driver Exists.
+        INT     2FH                     ;
+        CMP     AL,80H                  ;
+        MOV     AX,0                    ;
+        JNE     XMS_setup01             ; Return 0 if not.
+        MOV     AX,4310H                ; If so, set the driver's function
+        INT     2FH                     ;  address.
+        MOV     [XMSfunctAdr],BX        ;
+        MOV     [XMSfunctAdr+2],ES      ;
+        MOV     AX,1                    ; Return 1.
+  XMS_setup01:
+        POP     BX
+        POP     ES
+        POP     DS
+        RET
+XMS_setup               ENDP
+SUBTTL  LSHL - Shift an unsigned long left
+PAGE+
+ ;****************************************************************************
+ ;* 
+ ;* Shift an unsigned long integer left n number of bits.
+ ;*
+ ;****************************************************************************
+ ;
+ ; Stack frame definition for void LSHL( unsigned long *SHLnumber, unsigned n
+);
+ ;
+LSHLparms STRUC
+        DW      0, 0
+        DW      0
+SHLadr   DD      ?
+SHLn     DW      ?
+LSHLparms ENDS
+procname  LSHL
+        PUSH    BP
+        MOV     BP,SP
+        PUSH    BX
+        PUSH    CX
+        PUSH    DX
+        PUSH    DS
+        LDS     BX,SHLadr[BP]
+        MOV     CX,SHLn[BP]
+        MOV     AX,[BX]                 ; Get the long integer.
+        MOV     DX,[BX+2]               ; 
+ LSHL_01:
+        SHL     AX,1                    ; Do the long shift.
+        RCL     DX,1                    ; 
+        LOOP    LSHL_01                 ; 
+        MOV     [BX],AX                 ; Replace the addressed number.
+        MOV     [BX+2],DX               ; 
+        POP     DS
+        POP     DX
+        POP     CX
+        POP     BX
+        POP     BP
+        RET                             ; Exit
+endproc   LSHL
+SUBTTL  Extended Memory - Stack template for EXTget, EXTput
+PAGE+
+EXTgpparms STRUC
+        DW      0, 0
+        DW      0
+extgpBase    DW      ?
+extgpblk     DW      ?
+extgpblkAdr  DW      ?
+extgpBytes   DW      ?
+extgpmemAdr  DW      ?
+             DW      ?
+EXTgpparms ENDS
+SUBTTL  Extended Memory - XMS - Return total XMS memory.
+PAGE+
+ ; Use this function to detect wether or not XMS driver installed
+ ;
+ ; Stack frame definition for unsigned XMS_available( void );
+ ;
+ ;  The total XMS memory available (in 16k blocks) is returned.
+ ;
+procname  XMS_available
+        PUSH    BX
+        PUSH    CX
+        PUSH    DX
+        CALL    XMS_setup               ; Ensure XMS memory is set.
+        TEST    AX,AX                   ;
+        JZ      XMS_available01         ; Return zero if not.
+        MOV     AH,08H                  ; Set the size function code.
+        CALL    DWORD PTR CS:[XMSfunctAdr] ; Get the size.
+        TEST    AX,AX                   ;
+        JZ      XMS_available01         ;
+        MOV     AX,DX                   ; Set available Kbytes.
+        SUB     AX,64                   ; Subtract out the HMA (HIMEM.SYS
+bug).
+        JNC     XMS_available01         ;
+        XOR     AX,AX                   ; Set zero if underflow.
+  XMS_available01:
+        MOV     CL,4                    ; Divide Kbytes by 16 for blocks.
+        SHR     AX,CL                   ;
+        POP     DX
+        POP     CX
+        POP     BX
+        RET                             ; Exit
+endproc   XMS_available
+SUBTTL  Extended Memory - XMS - Return largest block XMS mem.
+PAGE+
+ ;
+ ; Stack frame definition for unsigned XMSblk_available( void );
+ ;
+ ;  The size of the largest block of XMS memory available,
+ ;  (in 16Kbyte blocks) is returned.
+ ;
+procname  XMSblk_available
+        PUSH    BX
+        PUSH    CX
+        PUSH    DX
+        CALL    XMS_setup               ; Ensure XMS memory is set.
+        TEST    AX,AX                   ;
+        JZ      XMSblk_available01      ; Return zero if not.
+        MOV     AH,08H                  ; Set the size function code.
+        CALL    DWORD PTR CS:[XMSfunctAdr] ; Get the size.
+        TEST    AX,AX                   ;
+        JZ      XMSblk_available01      ;
+        SUB     DX,64                   ; Subtract out the HMA (HIMEM.SYS
+bug).
+        JNC     XMSblk_available0X      ;
+        XOR     DX,DX                   ; Set zero if underflow.
+ XMSblk_available0X:
+        CMP     AX,DX                   ;
+        JBE     XMSblk_available01      ;
+        MOV     AX,DX                   ; Set available Kbytes.
+  XMSblk_available01:
+        MOV     CL,4                    ; Divide Kbytes by 16 for blocks.
+        SHR     AX,CL                   ;
+        POP     DX
+        POP     CX
+        POP     BX
+        RET                             ; Exit
+endproc   XMSblk_available
+SUBTTL  Extended Memory - XMS De-allocate a memory block.
+PAGE+
+ ;
+ ; Stack frame definition for int XMS_dealloc( int Hdl );
+ ;
+ ; Zero is returned if the operation fails, non-zero if success.
+ ;
+ ; its really important to do this, only other way to recover
+ ; XMS blocks is to re-boot
+XMSdealparms STRUC
+        DW      0, 0
+        DW      0
+xmsdealHdl  DW      ?
+XMSdealparms ENDS
+procname  XMS_dealloc
+        PUSH    BP
+        MOV     BP,SP
+        PUSH    BX
+        PUSH    DX
+;        CALL    XMS_setup               ; Ensure XMS memory is set.
+;        TEST    AX,AX                   ;
+;        JZ      XMS_dealloc01           ; Return zero if not.
+        MOV     DX,xmsdealHdl[BP]       ; Get the handle to de-allocate.
+        MOV     AH,0AH                  ;
+        CALL    DWORD PTR CS:[XMSfunctAdr] ; De-allocate it.
+  XMS_dealloc01:
+        POP     DX
+        POP     BX
+        POP     BP
+        RET                             ; Exit
+endproc   XMS_dealloc
+SUBTTL  Extended Memory - XMS Allocate a memory block.
+PAGE+
+ ;
+ ; Stack frame definition for int XMS_alloc( unsigned rsrvd, *size );
+ ;
+ ;     rsrved and size are in 16K byte blocks.
+ ;     rsrved is mem set aside for EMS, generaly zero
+ ;
+ ;  Zero is returned if the operation fails.
+ ;  Block (XMS) handle is returned if success.
+ ;
+ ;  size - is reduced by the amount of XMS memory actually allocated.
+ ;
+XMSalparms STRUC
+        DW      0, 0
+        DW      0
+xmsalrsrvd DW      ?
+xmsalsize  DD      ?
+XMSalparms ENDS
+procname  XMS_alloc
+        PUSH    BP
+        MOV     BP,SP
+        PUSH    BX
+        PUSH    CX
+        PUSH    DX
+        PUSH    DI
+        PUSH    ES
+        PUSH    DS
+        MOV     AX,CS                   ; Set the data segment to the code
+        MOV     DS,AX                   ;  segment.
+        MOV     CX,4                    ;
+        ADD     xmsalrsrvd[BP],CX       ; Subtract out the HMA (HIMEM.SYS
+bug).
+        SHL     xmsalrsrvd[BP],CL       ; Convert reserved blocks to K-bytes.
+        LES     DI,xmsalsize[BP]        ; Load size address.
+        XOR     AX,AX                   ;
+        MOV     BX,ES:[DI]              ; Get the requested size in blocks.
+        TEST    BX,0F000H               ; Check for more than 64 Megabytes.
+        JZ      XMS_alloc01             ;
+        MOV     BX,00FFFH               ;
+  XMS_alloc01:
+        MOV     CL,4                    ;
+        SHL     BX,CL                   ; Convert to K-Bytes.
+        MOV     CX,BX                   ; In CX.
+        JZ      XMS_alloc05             ; Return zero if no size requested.
+;        CALL    XMS_setup               ; Ensure XMS memory is set.
+;        TEST    AX,AX                   ;
+;        JZ      XMS_alloc05             ; Return zero if not.
+        XOR     BX,BX                   ;
+        MOV     AH,08H                  ; Set to Query Free XMS Memory.
+        CALL    DWORD PTR [XMSfunctAdr] ;
+        SUB     DX,xmsalrsrvd[BP]       ; Subtract out reserved blocks.
+        JB      XMS_alloc03             ; Ensure no borrow.
+        CMP     AX,DX                   ;
+        JBE     XMS_alloc02             ;
+        MOV     AX,DX                   ;
+  XMS_alloc02:
+        MOV     DX,AX                   ;
+        CMP     AX,68                   ; Ensure enough memory to allocate.
+  XMS_alloc03:
+        MOV     AX,0                    ;
+        JB      XMS_alloc05             ; Exit if not.
+        CMP     BL,80H                  ; Check for errors.
+        JE      XMS_alloc05             ;
+        CMP     BL,81H                  ;
+        JE      XMS_alloc05             ;
+        CMP     CX,DX                   ; Check actual against requested size.
+        JBE     XMS_alloc04             ;
+        MOV     CX,DX                   ; Set if actual < requested.
+  XMS_alloc04:
+        MOV     DX,CX                   ; Set requested size.
+        MOV     AH,09H                  ;
+        CALL    DWORD PTR [XMSfunctAdr] ; Allocate it.
+        DEC     AX                      ; Check for errors.
+        MOV     AX,0                    ;
+        JNZ     XMS_alloc05             ;
+        MOV     AX,CX                   ; Convert allocated size in KBytes
+        MOV     CL,4                    ; to allocated blocks.
+        SHR     AX,CL                   ;
+        SUB     ES:[DI],AX              ; Subtract the blocks allocated.
+        MOV     AX,DX                   ; Set to return the handle.
+  XMS_alloc05:
+        POP     DS
+        POP     ES
+        POP     DI
+        POP     DX
+        POP     CX
+        POP     BX
+        POP     BP
+        RET                             ; Exit
+endproc  XMS_alloc
+SUBTTL  Extended Memory - XMS get, put Stack Frame definition
+PAGE+
+XMSgpparms STRUC
+        DW      0, 0
+        DW      0
+xmsgpHdl     DW      ?
+xmsgpblk     DW      ?
+xmsgpblkAdr  DW      ?
+xmsgpBytes   DW      ?
+xmsgpmemAdr  DD      ?
+XMSgpparms ENDS
+SUBTTL  Extended Memory - XMStoMem
+PAGE+
+ ;
+ ; Stack frame definition for int XMStoMem( unsigned Handle,
+ ;                                          unsigned blk,
+ ;                                          unsigned blkAdr,
+ ;                                          unsigned Bytes,
+ ;                                          char     *memAdr
+ ;                                        );
+ ;
+ ;  XMSmemError is returned if the operation fails, Zero if success.
+ ;
+procname  XMStoMem
+        PUSH    BP
+        MOV     BP,SP
+        PUSH    BX
+        PUSH    CX
+        PUSH    DX
+        PUSH    SI
+        PUSH    DI
+        PUSH    ES
+        PUSH    DS
+        MOV     AX,CS                   ; Set Data Segment to Code Segment.
+        MOV     DS,AX                   ;
+        MOV     CX,xmsgpBytes[BP]       ; Get the number of bytes to transfer.
+        LES     BX,xmsgpmemAdr[BP]      ; Get the memory address.
+        MOV     DX,xmsgpHdl[BP]         ; Get the XMS handle.
+        MOV     [XMSmainGET.SrcHandle],DX ; Set it in the move structures.
+        MOV     [XMSwordGET.SrcHandle],DX ;
+        XOR     DX,DX                   ;
+        MOV     DI,xmsgpblk[BP]         ; Get the block number.
+        SHR     DI,1                    ; Form the 32 bit XMS address in
+        RCR     DX,1                    ;  DI:DX.
+        SHR     DI,1                    ;
+        RCR     DX,1                    ;
+        ADD     DX,xmsgpblkAdr[BP]      ;
+        TEST    CX,1                    ; Check for an odd number of bytes
+        JZ      XMStoMem02              ;  to transfer.
+        DEC     CX                      ; Decrement to an even number of
+bytes.
+        TEST    DX,1                    ; Check for an odd XMS address.
+        JZ      XMStoMem01              ;
+                                        ; XMS address is odd.
+                                        ; -------------------
+        DEC     DX                      ;
+        MOV     [XMSwordGET.SrcOffset],DX   ; Set the XMS address.
+        MOV     [XMSwordGET.SrcOffsetX],DI  ;
+        MOV     AH,0BH                  ; Set the XMS move, function code.
+        MOV     SI,OFFSET XMSwordGET    ; Set address of the move structure.
+        PUSH    BX                      ;
+        CALL    DWORD PTR [XMSfunctAdr] ; Call the XMS handler.
+        POP     BX                      ;
+        DEC     AX                      ; Check for errors.
+        JNZ     XMStoMem03              ; Error out if error.
+        MOV     AX,[XMSword]            ; Get the moved word.
+        MOV     ES:[BX],AH              ; Move the odd byte to memory.
+        INC     BX                      ; Reset the memory address.
+        ADD     DX,2                    ; And the XMS address.
+        JMP     XMStoMem02              ; Move the block.
+  XMStoMem01:
+                                        ; XMS address is even.
+                                        ; --------------------
+        ADD     DX,CX                   ;
+        MOV     [XMSwordGET.SrcOffset],DX   ; Set the XMS address.
+        SUB     DX,CX                       ;
+        MOV     [XMSwordGET.SrcOffsetX],DI  ;
+        MOV     AH,0BH                  ; Set the XMS move, function code.
+        MOV     SI,OFFSET XMSwordGET    ; Set address of the move structure.
+        PUSH    BX                      ;
+        CALL    DWORD PTR [XMSfunctAdr] ; Call the XMS handler.
+        POP     BX                      ;
+        DEC     AX                      ; Check for errors.
+        JNZ     XMStoMem03              ; Error out if error.
+        MOV     AX,[XMSword]            ; Get the moved word.
+        XCHG    DI,CX                   ;
+        MOV     ES:[BX+DI],AL           ; Move the odd byte to memory.
+        XCHG    DI,CX                   ;
+  XMStoMem02:
+        JCXZ    XMStoMem04              ; Avoid a zero byte move.
+        MOV     XMSmainGET.Length,CX    ; Set length for the move.
+        MOV     XMSmainGET.DestOffset,BX   ; Set Memory address.
+        MOV     XMSmainGET.DestOffsetX,ES  ;
+        MOV     XMSmainGET.SrcOffset,DX    ; Set XMS address.
+        MOV     XMSmainGET.SrcOffsetX,DI   ;
+        MOV     AH,0BH                  ; Set the XMS move, function code.
+        MOV     SI,OFFSET XMSmainGET    ; Set address of the move structure.
+        CALL    DWORD PTR [XMSfunctAdr] ; Call the XMS handler.
+        DEC     AX                      ; Check for errors.
+        JZ      XMStoMem05
+  XMStoMem03:
+        MOV     AX,XMSmemError          ; Set error code if error.
+        JMP     XMStoMem05              ;
+  XMStoMem04:
+        XOR     AX,AX                   ;
+  XMStoMem05:
+        POP     DS
+        POP     ES
+        POP     DI
+        POP     SI
+        POP     DX
+        POP     CX
+        POP     BX
+        POP     BP
+        RET                             ; Exit
+endproc  XMStoMem
+SUBTTL  Extended Memory - MemToXMS
+PAGE+
+ ;
+ ; Stack frame definition for int MemToXMS( unsigned Handle,
+ ;                                        unsigned blk,
+ ;                                        unsigned blkAdr,
+ ;                                        unsigned Bytes,
+ ;                                        char     *memAdr
+ ;                                       );
+ ;
+ ;  XMSmemError is returned if the operation fails, Zero if success.
+ ;
+procname  MemToXMS
+        PUSH    BP
+        MOV     BP,SP
+        PUSH    BX
+        PUSH    CX
+        PUSH    DX
+        PUSH    SI
+        PUSH    DI
+        PUSH    ES
+        PUSH    DS
+        MOV     AX,CS                   ;
+        MOV     DS,AX                   ;
+        MOV     CX,xmsgpBytes[BP]       ; Get the number of bytes to transfer.
+        LES     BX,xmsgpmemAdr[BP]      ; Get the memory address.
+        MOV     DX,xmsgpHdl[BP]         ; Get the XMS handle.
+        MOV     [XMSmainPUT.DestHandle],DX ; Set it in the move structures.
+        MOV     [XMSwordPUT.DestHandle],DX ;
+        MOV     [XMSwordGET.SrcHandle],DX  ;
+        XOR     DX,DX                   ;
+        MOV     DI,xmsgpblk[BP]         ; Get the block number.
+        SHR     DI,1                    ; Form the 32 bit XMS address in
+        RCR     DX,1                    ;  DI:DX.
+        SHR     DI,1                    ;
+        RCR     DX,1                    ;
+        ADD     DX,xmsgpblkAdr[BP]      ;
+        TEST    CX,1                    ; Check for an odd number of bytes
+        JZ      MemToXMS02              ;  to transfer.
+        DEC     CX                      ; Decrement to an even number of
+bytes.
+        TEST    DX,1                    ; Check for an odd XMS address.
+        JZ      MemToXMS01              ;
+                                        ; XMS address is odd.
+                                        ; -------------------
+        DEC     DX                      ;
+        MOV     [XMSwordGET.SrcOffset],DX   ; Set the XMS address.
+        MOV     [XMSwordGET.SrcOffsetX],DI  ;
+        MOV     [XMSwordPUT.DestOffset],DX  ;
+        MOV     [XMSwordPUT.DestOffsetX],DI ;
+        MOV     AH,0BH                  ; Set the XMS move, function code.
+        MOV     SI,OFFSET XMSwordGET    ; Set address of the move structure.
+        PUSH    BX                      ;
+        CALL    DWORD PTR [XMSfunctAdr] ; Call the XMS handler.
+        POP     BX                      ;
+        DEC     AX                      ; Check for errors.
+        JNZ     MemToXMS03              ; Error out if error.
+        MOV     AH,ES:[BX]              ; Get the odd memory byte.
+        MOV     [XMSwordByte+1],AH      ; Put it in the moved word.
+        MOV     AH,0BH                  ; Set the XMS move, function code.
+        MOV     SI,OFFSET XMSwordPUT    ; Set address of the move structure.
+        PUSH    BX                      ;
+        CALL    DWORD PTR [XMSfunctAdr] ; Call the XMS handler.
+        POP     BX                      ;
+        DEC     AX                      ; Check for errors.
+        JNZ     MemToXMS03              ; Error out if error.
+        INC     BX                      ; Reset the memory address.
+        ADD     DX,2                    ; And the XMS address.
+        JMP     MemToXMS02              ; Move the block.
+  MemToXMS01:
+                                        ; XMS address is even.
+                                        ; --------------------
+        ADD     DX,CX                   ;
+        MOV     [XMSwordGET.SrcOffset],DX   ; Set the XMS address.
+        MOV     [XMSwordPUT.DestOffset],DX  ;
+        SUB     DX,CX                       ;
+        MOV     [XMSwordGET.SrcOffsetX],DI  ;
+        MOV     [XMSwordPUT.DestOffsetX],DI ;
+        MOV     AH,0BH                  ; Set the XMS move, function code.
+        MOV     SI,OFFSET XMSwordGET    ; Set address of the move structure.
+        PUSH    BX                      ;
+        CALL    DWORD PTR [XMSfunctAdr] ; Call the XMS handler.
+        POP     BX                      ;
+        DEC     AX                      ; Check for errors.
+        JNZ     MemToXMS03              ; Error out if error.
+        XCHG    DI,CX                   ;
+        MOV     AL,ES:[BX+DI]           ; Get the odd memory byte.
+        XCHG    DI,CX                   ;
+        MOV     [XMSwordByte],AL        ; Set the moved word.
+        MOV     AH,0BH                  ; Set the XMS move, function code.
+        MOV     SI,OFFSET XMSwordPUT    ; Set address of the move structure.
+        PUSH    BX                      ;
+        CALL    DWORD PTR [XMSfunctAdr] ; Call the XMS handler.
+        POP     BX                      ;
+        DEC     AX                      ; Check for errors.
+        JNZ     MemToXMS03              ; Error out if error.
+  MemToXMS02:
+        JCXZ    MemToXMS04              ; Avoid a zero byte move.
+        MOV     XMSmainPUT.Length,CX    ; Set length for the move.
+        MOV     XMSmainPUT.SrcOffset,BX    ; Set Memory address.
+        MOV     XMSmainPUT.SrcOffsetX,ES   ;
+        MOV     XMSmainPUT.DestOffset,DX   ; Set XMS address.
+        MOV     XMSmainPUT.DestOffsetX,DI  ;
+        MOV     AH,0BH                  ; Set the XMS move, function code.
+        MOV     SI,OFFSET XMSmainPUT    ; Set address of the move structure.
+        CALL    DWORD PTR [XMSfunctAdr] ; Call the XMS handler.
+        DEC     AX                      ; Check for errors.
+        JZ      MemToXMS05
+  MemToXMS03:
+        MOV     AX,XMSmemError          ; Set error code if error.
+        JMP     MemToXMS05              ;
+  MemToXMS04:
+        XOR     AX,AX                   ;
+  MemToXMS05:
+        POP     DS
+        POP     ES
+        POP     DI
+        POP     SI
+        POP     DX
+        POP     CX
+        POP     BX
+        POP     BP
+        RET                             ; Exit
+endproc  MemToXMS
+SUBTTL  Last Page
+PAGE+
+pwrlolvl_TEXT   ENDS
+        END
diff --git a/src/lib/xmem/xmem.h b/src/lib/xmem/xmem.h
new file mode 100644 (file)
index 0000000..15df92c
--- /dev/null
@@ -0,0 +1,79 @@
+#if !defined(_XMEM_H)
+#define _XMEM_H
+typedef struct xms_node
+   {
+   long start, size, off;
+   short used;
+   struct xms_node *next;
+   }
+xms_node_t;
+typedef struct
+   {
+   int handle;
+   unsigned long total;
+   unsigned long avail;
+   unsigned long next_off;
+   xms_node_t *next;
+   }
+xms_head_t;
+#define XMSBLOCK 16384u
+#define XMSBLOCKSHIFT 14
+extern void LSHL( unsigned long far *SHLnumber, unsigned short n );
+extern unsigned short XMS_available( void );
+extern unsigned short XMSblk_available( void );
+extern short XMS_alloc(unsigned short rsrvd,
+                       unsigned short far *size
+                      );
+extern short XMS_dealloc(unsigned short Hdl );
+extern short XMStoMem(unsigned short Handle,   // XMS handle returned by
+//XMS_alloc()
+                      unsigned short blk,      // which 16k block to copy to
+                      unsigned short blkAdr,   // offset within 16k block
+                      unsigned short Bytes,    // bytes to copy
+                      void   far *memAdr
+                     );
+extern short MemToXMS(unsigned short Handle,
+                      unsigned short blk,
+                      unsigned short blkAdr,
+                      unsigned short Bytes,
+                      void   far *memAdr
+                     );
+// call these for ease
+short alloc_xms(unsigned short far *size);  // size in 16k blocks
+// NOTE size is changed to the amount block size was altered by!
+// normaly this is zero
+short xms_to_mem(unsigned short handle, void far *p, unsigned long off,
+unsigned short n);
+short mem_to_xms(unsigned short handle, void far *p, unsigned long off,
+unsigned short n);
+void deinit_xms(void);
+short init_xms(unsigned short min_blocks);
+void qfree_xms(xms_node_t *node);
+xms_node_t *qalloc_xms(unsigned long size);
+xms_node_t *xms_open(char *file);
+short xms_read(void far *buffer, unsigned short n, xms_node_t *node);
+short xms_write(void far *buffer, unsigned short n, xms_node_t *node);
+long xms_tell(xms_node_t *node);
+short xms_seek(xms_node_t *node, long off, short whence);
+void xms_close(xms_node_t *node);
+extern xms_head_t xms_head;
+#endif
+/* ---------------------------------- end of file --------------------- */
diff --git a/src/lib/xmem/xmem.txt b/src/lib/xmem/xmem.txt
new file mode 100644 (file)
index 0000000..306374c
--- /dev/null
@@ -0,0 +1,33 @@
+Newsgroups: rec.games.programmer
+From: alexad3@icebox.iceonline.com (Alexander J. Russell)
+Subject: xms for x2ftp.oulu.fi
+Date: Mon, 20 Mar 1995 08:16:21 GMT
+
+Can some kind soul please pass this on to x2ftp.oulu.fi for me.
+I can't ftp from my currrent connection.
+
+This file contains 3 files:
+xmem.h   : c include file
+xmem.asm : low level basic XMS acess
+xmemc.c  : super easy C access via functions like fopen, fread, fwrite
+           xopen, xread, xwrite, xseek etc...
+FOR DOS REAL mode programs, requires HIMEM.SYS to be loaded in
+config.sys.
+... 
+
+This should be enough code to do what ever you want with XMS.
+I like to use the file style access to remind myself it isn't
+as fast as say memcpy().
+
+Cheers.
+
+
+
+
+The AnArChIsT - Anarchy! NOT Chaos! aka Alec Russell
+alexad3@icebox.iceonline.com
+
diff --git a/src/lib/xmem/xmemc.c b/src/lib/xmem/xmemc.c
new file mode 100644 (file)
index 0000000..0ab1de3
--- /dev/null
@@ -0,0 +1,316 @@
+/*
+   Written by Alexander J. Russell 1994
+   Placed in the public Domain by Alec Russell, March 1995
+   Slightly higher level xms calls than xmem.asm
+*/
+#include <stdio.h>
+#include <io.h>
+#include <string.h>
+#include <malloc.h>
+#include "src\lib\xmem\xmem.h"
+xms_head_t xms_head={0};  // set handle to zero
+/* ---------------------- alloc_xms() ----------------- February 19,1994 */
+short alloc_xms(unsigned short far *size)  // size in 16k blocks
+{
+   return(XMS_alloc(0, size));
+}
+/* ---------------------- xms_to_mem() ---------------- February 19,1994 */
+short xms_to_mem(unsigned short handle, void far *p, unsigned long off,
+unsigned short n)
+{
+   unsigned short block, boff;
+   block=off >> XMSBLOCKSHIFT;
+   boff=off - (block << XMSBLOCKSHIFT);
+   return(XMStoMem(handle, block, boff, n, p));
+}
+/* ---------------------- mem_to_xms() ---------------- February 19,1994 */
+short mem_to_xms(unsigned short handle, void far *p, unsigned long off,
+unsigned short n)
+{
+   unsigned short block, boff;
+   block=off >> XMSBLOCKSHIFT;
+   boff=off - (block << XMSBLOCKSHIFT);
+   return(MemToXMS(handle, block, boff, n, p));
+}
+/* ---------------------- qalloc_xms() -------------------- March 8,1994 */
+xms_node_t *qalloc_xms(unsigned long size)
+{
+   xms_node_t *node=NULL;
+   xms_node_t *t1;
+   if ( size <= xms_head.avail )
+      {
+      // look for existing node
+      t1=xms_head.next;
+      while ( t1 )
+         {
+         if ( t1->used == 0 && t1->size >= size )
+            {
+            t1->off=0;
+            t1->used=1;
+            node=t1;
+            break;
+            }
+         else
+            t1=t1->next;
+         }
+      if ( node == NULL ) // didn't find existing node
+         {
+         node=malloc(sizeof(xms_node_t));
+         if ( node )
+            {
+            node->off=0;
+            node->used=1;
+            node->size=size;
+            node->next=NULL;
+            node->start=xms_head.next_off;
+            xms_head.avail-=size;
+            xms_head.next_off+=size;
+            if ( xms_head.next == NULL )
+               {
+               xms_head.next=node;
+               }
+            else
+               {
+               t1=xms_head.next;
+               while ( t1->next )
+                  t1=t1->next;
+               t1->next=node;
+               }
+            }
+         else
+            printf("out of near mem in qalloc_xms");
+         }
+      }
+   else
+      printf("out of xms mem in qalloc size %lu avail %lu", size,
+xms_head.avail);
+   return(node);
+}
+/* ---------------------- qfree_xms() --------------------- March 8,1994 */
+void qfree_xms(xms_node_t *node)
+{
+   xms_node_t *t1;
+   if ( xms_head.next )
+      {
+      t1=xms_head.next;
+      while ( t1 != node && t1 )
+         t1=t1->next;
+      if ( t1 )
+         {
+         t1->used=0;
+         }
+      else
+         printf("ERROR didn't find node qfree");
+      }
+   else
+      {
+      printf("ATTEMPTED to qfree empty list");
+      }
+}
+/* ---------------------- xms_open() ---------------------- March 8,1994 */
+xms_node_t *xms_open(char *file)
+{
+   int i;
+   xms_node_t *node=NULL;
+   FILE *fp;
+   char *buffer;
+   unsigned long off;
+   fp=fopen(file, "rb");
+   if ( fp )
+      {
+      node=qalloc_xms(filelength(fileno(fp)));
+      if ( node )
+         {
+         buffer=malloc(4096);
+         if ( buffer )
+            {
+            off=0l;
+            while ( (i=fread(buffer, 1, 4096, fp)) )
+               {
+               mem_to_xms(xms_head.handle, (char far *)buffer,
+off+node->start, i);
+               off+=i;
+               }
+            free(buffer);
+            }
+         else
+            printf("out of mem in xms_open 1");
+         }
+      fclose(fp);
+      }
+   else
+      printf("ERROR opening %s in xms_open", file);
+   return(node);
+}
+/* ---------------------- xms_read() ---------------------- March 8,1994 */
+short xms_read(void far *buffer, unsigned short n, xms_node_t *node)
+{
+   if ( node->off >= node->size )
+      return 0;
+   if ( n+node->off > node->size )
+      n=node->size - node->off;
+   xms_to_mem(xms_head.handle, buffer, node->start+node->off, n);
+   node->off+=n;
+   return(n);
+}
+/* ---------------------- xms_write() ---------------------- March 8,1994 */
+short xms_write(void far *buffer, unsigned short n, xms_node_t *node)
+{
+   if ( node->off >= node->size )
+      return 0;
+   if ( n+node->off > node->size )
+      n=node->size - node->off;
+   mem_to_xms(xms_head.handle, buffer, node->start+node->off, n);
+   node->off+=n;
+   return(n);
+}
+/* ---------------------- xms_tell() ---------------------- March 8,1994 */
+long xms_tell(xms_node_t *node)
+{
+   return node->off;
+}
+/* ---------------------- xms_seek() ---------------------- March 8,1994 */
+short xms_seek(xms_node_t *node, long off, short whence)
+{
+   short err=0;
+   switch ( whence )
+      {
+      case SEEK_SET:
+         if ( off < 0l || off > node->size )
+            err=1;
+         else
+            node->off=off;
+         break;
+      case SEEK_END:
+         if ( off > 0l || (node->size + off) < 0l )
+            err=1;
+         else
+            node->off=node->size + off;
+         break;
+      case SEEK_CUR:
+         if ( node->off + off < 0l || node->off + off > node->size )
+            err=1;
+         else
+            node->off+=off;
+         break;
+      }
+   return(err);
+}
+/* ---------------------- xms_close() --------------------- March 8,1994 */
+void xms_close(xms_node_t *node)
+{
+   qfree_xms(node);
+}
+/* ---------------------- init_xms() ---------------------- March 8,1994 */
+short init_xms(unsigned short min_blocks)
+{
+   unsigned short blocks;
+   blocks=XMSblk_available();
+   if ( blocks >= min_blocks )
+      {
+      memset(&xms_head, 0, sizeof(xms_head_t));
+      if ( (xms_head.handle=alloc_xms(&blocks)) )
+         {
+         printf("blocks minus by = %u", blocks);
+         min_blocks-=blocks;
+         xms_head.avail=xms_head.total=(unsigned long)min_blocks*XMSBLOCK;
+         blocks=min_blocks;
+         }
+      else
+         blocks=0;
+      }
+   else
+      blocks=0;
+   return(blocks);
+}
+/* ---------------------- deinit_xms() -------------------- March 8,1994 */
+void deinit_xms(void)
+{
+   xms_node_t *t1, *t2;
+   if ( xms_head.handle )
+      {
+      XMS_dealloc(xms_head.handle);
+      if ( xms_head.next )
+         {
+         t1=xms_head.next;
+         t2=t1->next;
+         while ( t1 )
+            {
+            free(t1);
+            t1=t2;
+            t2=t1->next;
+            }
+         }
+      memset(&xms_head, 0, sizeof(xms_head_t));
+      }
+}
+/* --------------------------- end of file ------------------------- */
diff --git a/src/lib/xms.c b/src/lib/xms.c
new file mode 100644 (file)
index 0000000..1781754
--- /dev/null
@@ -0,0 +1,187 @@
+/* This file implements rudimentary XMS memory handling.
+ * Documentation on the XMS API was found on http://www.qzx.com/pc-gpe/xms30.txt
+ */
+
+#include "src\lib\xms.h"
+
+/* Set up the XMS driver, returns 0 on success and non-zero on failure */
+static int initxms(void)
+{
+       char XMSStatus = 0;
+
+       if ( XMSControl == 0 )
+       {
+               __asm {
+               ; Is an XMS driver installed?
+                       mov ax,4300h
+                       int 2Fh
+                       mov [XMSStatus], al
+               }
+
+               if ( XMSStatus == 0x80 )
+               {
+                       __asm {
+                       ; Get the address of the driver control function
+                               mov ax,4310h
+                               int 2Fh
+                               mov word ptr [XMSControl]  ,bx
+                               mov word ptr [XMSControl+2],es
+                       }
+               }
+       }
+
+       return ( XMSControl == 0 );
+}
+
+/* Allocate a slab of memory from XMS */
+void huge * xmsmalloc(long unsigned int size)
+{
+       unsigned int kB;
+       unsigned int XMSStatus = 0;
+       unsigned int XMSHandle = 0;
+       void huge * XMSPointer = NULL;
+       int n;
+
+       /* If we can not initialize XMS, the allocation fails */
+       if ( initxms() )
+               return NULL;
+
+       /* It is not possible to allocate more kilobytes than a 16-bit register can hold :-) */
+       if ( size / 1024 > UINT_MAX )
+               return NULL;
+
+       /* Get the next free entry in the handle <-> pointer mapping */
+       for ( n = 0; n < MAX_XMS_ALLOCATIONS; n++ )
+       {
+               if ( allocMapXMS[n].XMSPointer == NULL )
+                       break;
+       }
+
+       if ( n == MAX_XMS_ALLOCATIONS )
+               return NULL;
+
+       kB = size / 1024 + (size % 1024 > 0);
+
+       __asm {
+       ; Allocate [kB] kilobytes of XMS memory
+               mov ah, 09h
+               mov dx, [kB]
+               call [XMSControl]
+               mov [XMSStatus], ax
+               mov [XMSHandle], dx
+       }
+
+       /* Check if XMS allocation failed */
+       if ( XMSStatus == 0)
+               return NULL;
+
+       __asm {
+       ; Convert XMS handle to normal pointer
+               mov ah, 0Ch
+               mov dx, [XMSHandle]
+               call [XMSControl]
+               mov [XMSStatus], ax
+
+               mov word ptr [XMSPointer],  bx
+               mov word ptr [XMSPointer+2],dx
+       }
+
+       if ( XMSStatus == 0 )
+       {
+               /* Lock failed, deallocate the handle */
+               __asm {
+               ; Free XMS handle
+                       mov ah, 0Ah
+                       mov dx, [XMSHandle]
+                       call [XMSControl]
+
+               ; Return value is not really interesting 
+               ;   mov [XMSStatus], ax
+               }
+               return NULL;
+       }
+
+       /* Create an entry in the handle <-> pointer mapping */
+       allocMapXMS[n].XMSHandle = XMSHandle;
+       allocMapXMS[n].XMSPointer = XMSPointer;
+
+       return XMSPointer;
+}
+
+/* Free a pointer allocated with xmsalloc */
+void xmsfree(void huge * XMSPointer)
+{
+       int n;
+
+       if ( XMSPointer == NULL )
+               return;
+
+       if ( initxms() )
+               return;
+
+       for ( n = 0; n < MAX_XMS_ALLOCATIONS; n++ )
+       {
+               if ( allocMapXMS[n].XMSPointer == XMSPointer )
+               {
+                       int XMSHandle = allocMapXMS[n].XMSHandle;
+
+                       __asm {
+                       ; Unlock handle so we can free the memory block
+                               mov ah, 0Dh
+                               mov dx, [XMSHandle]
+                               call [XMSControl]
+
+                       ; Free XMS memory
+                               mov ah, 0Ah
+                               mov dx, [XMSHandle]
+                               call [XMSControl]
+
+                       ; Return value ignored
+                       }
+
+                       /* Clear handle <-> pointer map entry so it can be reused */
+                       allocMapXMS[n].XMSHandle = 0;
+                       allocMapXMS[n].XMSPointer = NULL;
+
+                       return;
+               }
+       }
+}
+
+/* Write a memory report for debugging purposes */
+void xmsreport(void/*FILE * stream*/)
+{
+       int XMSVersionNumber = 0;
+       unsigned int XMSLargestBlock = 0;
+       unsigned int XMSTotal = 0;
+
+       if ( initxms() )
+       {
+               puts("Could not initialize XMS Driver!");
+               return;
+       }
+
+       __asm {
+       ; Get the driver version number
+               mov ah,00h
+               call [XMSControl] ; Get XMS Version Number
+               mov [XMSVersionNumber], ax
+
+       ; Get the amount of free XMS memory
+               mov ah, 08h
+               call [XMSControl]
+               mov [XMSLargestBlock], ax
+               mov [XMSTotal], dx
+       }
+
+       //fprintf(stream, "XMS Version number: %d\n", XMSVersionNumber);
+       //fprintf(stream, "Largest available block: %d kB (%d kB total)\n", XMSLargestBlock, XMSTotal);
+       printf("XMS Version number: %d\n", XMSVersionNumber);
+       printf("Largest available block: %d kB (%d kB total)\n", XMSLargestBlock, XMSTotal);
+}
+
+/*int main()
+{
+       xmsreport(fopen("xms.log", "w"));
+       return 0;
+}*/
diff --git a/src/lib/xms.h b/src/lib/xms.h
new file mode 100644 (file)
index 0000000..b658749
--- /dev/null
@@ -0,0 +1,28 @@
+#ifndef _XMS_H_
+#define _XMS_H_
+#include <stddef.h> /* Definition of NULL */
+#include <limits.h> /* Definition of UINT_MAX */
+#include <stdio.h>  /* fprintf and (FILE *) */
+
+/* Allow external configuration of maximum concurrent XMS allocations */
+#ifndef MAX_XMS_ALLOCATIONS
+#define MAX_XMS_ALLOCATIONS 4
+#endif
+
+/* Address of the XMS driver */
+static long XMSControl;
+
+/* Mapping of XMS handle <-> normal pointer */
+typedef struct {
+       unsigned int XMSHandle;
+       void huge * XMSPointer;
+} XMSHandleMap;
+
+static XMSHandleMap allocMapXMS[MAX_XMS_ALLOCATIONS];
+
+static int initxms(void);
+void huge * xmsmalloc(long unsigned int size);
+void xmsfree(void huge * XMSPointer);
+void xmsreport(void/*FILE * stream*/);
+
+#endif/*_XMS_H_*/
index bcb089d..b91adf7 100644 (file)
@@ -2,10 +2,14 @@
 #include <stdio.h>\r
 #include <stdlib.h>\r
 #include "src\lib\dos_kb.h"\r
+#include "16\lib\x\modex.h"\r
 #include "src\lib\wtest\wtest.c"\r
+#include "src\lib\ems.c"\r
 \r
 //word far *clock= (word far*) 0x046C; /* 18.2hz clock */\r
 \r
+int emmhandle,ist;\r
+\r
 typedef struct {\r
        bitmap_t *data;\r
        word tileHeight;\r
@@ -39,12 +43,14 @@ typedef struct {
        int ty; //player tile position on the viewable map\r
        int triggerx; //player's trigger box tile position on the viewable map\r
        int triggery; //player's trigger box tile position on the viewable map\r
+       int setx; //NOT USED YET! player sprite sheet set on the image x\r
+       int sety; //NOT USED YET! player sprite sheet set on the image y\r
        word q; //loop variable\r
        word d; //direction\r
+       bitmap_t data; //supposively the sprite sheet data\r
        int hp; //hitpoints of the player\r
 } actor_t;\r
 \r
-\r
 map_t allocMap(int w, int h);\r
 void initMap(map_t *map);\r
 void mapScrollRight(map_view_t *mv, byte offset);\r
@@ -55,7 +61,7 @@ void mapGoTo(map_view_t *mv, int tx, int ty);
 void mapDrawTile(tiles_t *t, word i, page_t *page, word x, word y);\r
 void mapDrawRow(map_view_t *mv, int tx, int ty, word y);\r
 void mapDrawCol(map_view_t *mv, int tx, int ty, word x);\r
-void animatePlayer(map_view_t *src, map_view_t *dest, /*map_view_t *top, */short d1, short d2, int x, int y, int ls, int lp, bitmap_t *bmp);\r
+void animatePlayer(map_view_t *src, map_view_t *dest, /*map_view_t *top, */sword d, short scrolloffsetswitch, int x, int y, int ls, int lp, bitmap_t *bmp);\r
 \r
 #define TILEWH 16\r
 #define QUADWH (TILEWH/4)\r
@@ -67,28 +73,25 @@ void animatePlayer(map_view_t *src, map_view_t *dest, /*map_view_t *top, */short
 #define MAPY 150\r
 #define TRIGGX 10\r
 #define TRIGGY 9\r
-//#define SWAP(a, b) tmp=a; a=b; b=tmp;\r
+\r
 void main() {\r
-       bitmap_t ptmp; // player sprite\r
+       bitmap_t ptmp;//, npctmp; // player sprite\r
        const char *cpus;\r
        static int persist_aniframe = 0;    /* gonna be increased to 1 before being used, so 0 is ok for default */\r
        page_t screen, screen2, screen3;\r
        map_t map;\r
        map_view_t mv, mv2, mv3;\r
-       map_view_t *bg, *spri, *mask;//, *tmp;
+       map_view_t *bg, *spri, *mask;//, *tmp;\r
        byte *pal;\r
        byte *ptr;\r
        actor_t player;\r
+       //actor_t npc0;\r
 \r
-\r
-       /* save the palette */\r
-       pal  = modexNewPal();\r
-       modexPalSave(pal);\r
-       modexFadeOff(4, pal);\r
-       modexPalBlack();\r
+       if(isEMS() || checkEMS()){ printf("%d\n", coretotalEMS()); emmhandle = mallocEMS(coretotalEMS()); }\r
 \r
        /* create the map */\r
        map = allocMap(MAPX,MAPY); //20x15 is the resolution of the screen you can make maps smaller than 20x15 but the null space needs to be drawn properly\r
+       //if(isEMS()) printf("%d tesuto\n", coretotalEMS());\r
        initMap(&map);\r
        mv.map = &map;\r
        mv2.map = &map;\r
@@ -96,8 +99,46 @@ void main() {
 \r
        /* draw the tiles */\r
        ptr = map.data;\r
-       /*data\\*/\r
+       /* data */\r
        ptmp = bitmapLoadPcx("ptmp.pcx"); // load sprite\r
+       //npctmp = bitmapLoadPcx("ptmp1.pcx"); // load sprite\r
+\r
+       /*if(isEMS())\r
+       {\r
+               XMOVE mm;\r
+               mm.length=sizeof(map);\r
+               mm.sourceH=0;\r
+               mm.sourceOff=(long)&map;\r
+               mm.destH=emmhandle;\r
+               mm.destOff=1;\r
+               //halp!\r
+               ist = move_emem(&mm);\r
+               printf("%d\n", coretotalEMS());\r
+               if(!ist){ dealloc_emem(emmhandle); exit(5); }\r
+               //printf("%d\n", emmhandle);\r
+       }\r
+\r
+       if(isEMS())\r
+       {\r
+               XMOVE mm;\r
+               mm.length=emmhandle;\r
+               mm.sourceH=0;\r
+               mm.sourceOff=(long)&ptmp;\r
+               mm.destH=emmhandle;\r
+               mm.destOff=0;\r
+               //halp!\r
+               ist = move_emem(&mm);\r
+               printf("%d\n", coretotalEMS());\r
+               if(!ist){ dealloc_emem(emmhandle); exit(5); }\r
+               //printf("%d\n", emmhandle);\r
+       }\r
+*/\r
+       /* save the palette */\r
+       pal  = modexNewPal();\r
+       modexPalSave(pal);\r
+       modexFadeOff(4, pal);\r
+       modexPalBlack();\r
+\r
        setkb(1);\r
        modexEnter();\r
        modexPalBlack();\r
@@ -123,7 +164,7 @@ void main() {
        //mapGoTo(mask, 0, 0);\r
 \r
        //TODO: put player in starting position of spot\r
-       //default player position on the viewable map
+       //default player position on the viewable map\r
        player.tx = bg->tx + 10;\r
        player.ty = bg->ty + 8;\r
        player.x = player.tx*TILEWH;\r
@@ -132,21 +173,150 @@ void main() {
        player.triggery = player.ty+1;\r
        player.q=1;\r
        player.d=0;\r
+       player.hp=4;\r
+       //npc\r
+       /*npc0.tx = bg->tx + 1;\r
+       npc0.ty = bg->ty + 1;\r
+       npc0.x = npc0.tx*TILEWH;\r
+       npc0.y = npc0.ty*TILEWH;\r
+       npc0.triggerx = npc0.tx;\r
+       npc0.triggery = npc0.ty+1;\r
+       npc0.q=1;\r
+       npc0.d=0;\r
+       modexDrawSpriteRegion(spri->page, npc0.x-4, npc0.y-TILEWH, 24, 64, 24, 32, &npctmp);*/\r
        modexDrawSpriteRegion(spri->page, player.x-4, player.y-TILEWH, 24, 64, 24, 32, &ptmp);\r
 \r
        modexClearRegion(spri->page, player.triggerx*16, player.triggery*16, 16, 16, 1);\r
        modexClearRegion(bg->page, player.triggerx*16, player.triggery*16, 16, 16, 1);\r
+\r
+       modexClearRegion(spri->page, 5*16, 5*16, 16, 16, 255);\r
+       modexClearRegion(bg->page, 5*16, 5*16, 16, 16, 255);\r
+\r
        modexShowPage(spri->page);\r
-       while(!keyp(1))\r
+       while(!keyp(1) && player.hp>0)\r
        {\r
        //top left corner & bottem right corner of map veiw be set as map edge trigger since maps are actually square\r
        //to stop scrolling and have the player position data move to the edge of the screen with respect to the direction\r
        //when player.tx or player.ty == 0 or player.tx == 20 or player.ty == 15 then stop because that is edge of map and you do not want to walk of the map\r
-       #define INC_PER_FRAME if(player.q&1) persist_aniframe++; if(persist_aniframe>4) persist_aniframe = 1;
-
+       #define INC_PER_FRAME if(player.q&1) persist_aniframe++; if(persist_aniframe>4) persist_aniframe = 1;\r
+       /*#define INC_PER_FRAME_NPC if(npc0.q&1) persist_aniframe++; if(persist_aniframe>4) persist_aniframe = 1;\r
+\r
+       if(npc0.d == 0 && npc0.q == 1) npc0.d =rand()%8;\r
+       if(npc0.d>4)\r
+               npc0.d=0;\r
+\r
+       //right movement\r
+       if(npc0.d == 2)\r
+       {\r
+               if(npc0.tx < MAPX && !(npc0.tx+1 == TRIGGX && npc0.ty == TRIGGY) && !(npc0.tx+1 == player.tx && npc0.ty == player.ty))\r
+               {\r
+                       if(npc0.q<=(TILEWH/SPEED))\r
+                       {\r
+                               INC_PER_FRAME_NPC;\r
+                               npc0.x+=SPEED;\r
+                               //animatePlayer(bg, spri, mask, 1, 0, npc0.x, npc0.y, persist_aniframe, q, &npctmp);\r
+                               animatePlayer(bg, spri, npc0.d-1, 0, npc0.x, npc0.y, persist_aniframe, npc0.q, &npctmp);\r
+                               modexShowPage(spri->page);\r
+                               npc0.q++;\r
+                       } else { npc0.q = 1; npc0.d = 0; npc0.tx++; }\r
+               }\r
+               else\r
+               {\r
+                       modexCopyPageRegion(spri->page, bg->page, npc0.x-4, npc0.y-TILEWH, npc0.x-4, npc0.y-TILEWH, 24, 32);\r
+                       modexDrawSpriteRegion(spri->page, npc0.x-4, npc0.y-TILEWH, 24, 32, 24, 32, &npctmp);\r
+                       modexShowPage(spri->page);\r
+                       npc0.d = 0;\r
+               }\r
+               npc0.triggerx = npc0.tx+1;\r
+               npc0.triggery = npc0.ty;\r
+       }\r
+\r
+       //left movement\r
+       if(npc0.d == 4)\r
+       {\r
+               if(npc0.tx > 1 && !(npc0.tx-1 == TRIGGX && npc0.ty == TRIGGY) && !(npc0.tx-1 == player.tx && npc0.ty == player.ty))\r
+               {\r
+                       if(npc0.q<=(TILEWH/SPEED))\r
+                       {\r
+                               INC_PER_FRAME_NPC;\r
+                               npc0.x-=SPEED;\r
+                               //animatePlayer(bg, spri, mask, 3, 0, npc0.x, npc0.y, persist_aniframe, q, &npctmp);\r
+                               animatePlayer(bg, spri, npc0.d-1, 0, npc0.x, npc0.y, persist_aniframe, npc0.q, &npctmp);\r
+                               modexShowPage(spri->page);\r
+                               npc0.q++;\r
+                       } else { npc0.q = 1; npc0.d = 0; npc0.tx--; }\r
+               }\r
+               else\r
+               {\r
+                       modexCopyPageRegion(spri->page, bg->page, npc0.x-4, npc0.y-TILEWH, npc0.x-4, npc0.y-TILEWH, 24, 32);\r
+                       modexDrawSpriteRegion(spri->page, npc0.x-4, npc0.y-TILEWH, 24, 96, 24, 32, &npctmp);\r
+                       modexShowPage(spri->page);\r
+                       npc0.d = 0;\r
+               }\r
+               npc0.triggerx = npc0.tx-1;\r
+               npc0.triggery = npc0.ty;\r
+       }\r
+\r
+       //down movement\r
+       if(npc0.d == 3)\r
+       {\r
+               if(npc0.ty < MAPY && !(npc0.tx == TRIGGX && npc0.ty+1 == TRIGGY) && !(npc0.tx == player.tx && npc0.ty == player.ty+1))\r
+               {\r
+                       if(npc0.q<=(TILEWH/SPEED))\r
+                       {\r
+                               INC_PER_FRAME_NPC;\r
+                               npc0.y+=SPEED;\r
+                               //animatePlayer(bg, spri, mask, 2, 0, npc0.x, npc0.y, persist_aniframe, q, &npctmp);\r
+                               animatePlayer(bg, spri, npc0.d-1, 0, npc0.x, npc0.y, persist_aniframe, npc0.q, &npctmp);\r
+                               modexShowPage(spri->page);\r
+                               npc0.q++;\r
+                       } else { npc0.q = 1; npc0.d = 0; npc0.ty++; }\r
+               }\r
+               else\r
+               {\r
+                       modexCopyPageRegion(spri->page, bg->page, npc0.x-4, npc0.y-TILEWH, npc0.x-4, npc0.y-TILEWH, 24, 32);\r
+                       modexDrawSpriteRegion(spri->page, npc0.x-4, npc0.y-TILEWH, 24, 64, 24, 32, &npctmp);\r
+                       modexShowPage(spri->page);\r
+                       npc0.d = 0;\r
+               }\r
+               npc0.triggerx = npc0.tx;\r
+               npc0.triggery = npc0.ty+1;\r
+       }\r
+\r
+       //up movement\r
+       if(npc0.d == 1)\r
+       {\r
+               if(npc0.ty > 1 && !(npc0.tx == TRIGGX &&  npc0.ty-1 == TRIGGY) && !(npc0.tx+1 == player.tx && npc0.ty == player.ty-1))\r
+               {\r
+                       if(npc0.q<=(TILEWH/SPEED))\r
+                       {\r
+                               INC_PER_FRAME_NPC;\r
+                               npc0.y-=SPEED;\r
+                               //animatePlayer(bg, spri, mask, 0, 0, npc0.x, npc0.y, persist_aniframe, q, &npctmp);\r
+                               modexShowPage(spri->page);\r
+                               animatePlayer(bg, spri, npc0.d-1, 0, npc0.x, npc0.y, persist_aniframe, npc0.q, &npctmp);\r
+                               npc0.q++;\r
+                       } else { npc0.q = 1; npc0.d = 0; npc0.ty--; }\r
+               }\r
+               else\r
+               {\r
+                       modexCopyPageRegion(spri->page, bg->page, npc0.x-4, npc0.y-TILEWH, npc0.x-4, npc0.y-TILEWH, 24, 32);\r
+                       modexDrawSpriteRegion(spri->page, npc0.x-4, npc0.y-TILEWH, 24, 0, 24, 32, &npctmp);\r
+                       modexShowPage(spri->page);\r
+                       npc0.d = 0;\r
+               }\r
+               npc0.triggerx = npc0.tx;\r
+               npc0.triggery = npc0.ty-1;\r
+       }\r
+\r
+       if((npc0.triggery == player.ty && npc0.triggerx == player.tx) || (npc0.ty == player.ty && npc0.tx == player.tx)){ player.hp--; }\r
+*/\r
+\r
+       //player movement\r
+       //TODO: make movement into a function!\r
        //right movement\r
        if((keyp(77) && !keyp(75) && player.d == 0) || player.d == 2)\r
-       {
+       {\r
                if(player.d == 0){ player.d = 2; }\r
                if(bg->tx >= 0 && bg->tx+20 < MAPX && player.tx == bg->tx + 10 && !(player.tx+1 == TRIGGX && player.ty == TRIGGY))\r
                {\r
@@ -183,11 +353,11 @@ void main() {
                }\r
                player.triggerx = player.tx+1;\r
                player.triggery = player.ty;\r
-       }
+       }\r
 \r
        //left movement\r
        if((keyp(75) && !keyp(77) && player.d == 0) || player.d == 4)\r
-       {
+       {\r
                if(player.d == 0){ player.d = 4; }\r
                if(bg->tx > 0 && bg->tx+20 <= MAPX && player.tx == bg->tx + 10 && !(player.tx-1 == TRIGGX && player.ty == TRIGGY))\r
                {\r
@@ -224,11 +394,11 @@ void main() {
                }\r
                player.triggerx = player.tx-1;\r
                player.triggery = player.ty;\r
-       }
+       }\r
 \r
        //down movement\r
        if((keyp(80) && !keyp(72) && player.d == 0) || player.d == 3)\r
-       {
+       {\r
                if(player.d == 0){ player.d = 3; }\r
                if(bg->ty >= 0 && bg->ty+15 < MAPY && player.ty == bg->ty + 8 && !(player.tx == TRIGGX && player.ty+1 == TRIGGY))\r
                {\r
@@ -266,10 +436,10 @@ void main() {
                player.triggerx = player.tx;\r
                player.triggery = player.ty+1;\r
        }\r
-
+\r
        //up movement\r
        if((keyp(72) && !keyp(80) && player.d == 0) || player.d == 1)\r
-       {
+       {\r
                if(player.d == 0){ player.d = 1; }\r
                if(bg->ty > 0 && bg->ty+15 <= MAPY && player.ty == bg->ty + 8 && !(player.tx == TRIGGX && player.ty-1 == TRIGGY))\r
                {\r
@@ -309,15 +479,17 @@ void main() {
        }\r
        //modexClearRegion(mask->page, 66, 66, 2, 40, 0);\r
 \r
-       if((player.triggerx == TRIGGX && player.triggery == TRIGGY) && keyp(KEY_ENTER))\r
+       if(((player.triggerx == TRIGGX && player.triggery == TRIGGY) && keyp(0x1C))||(player.tx == 5 && player.ty == 5))\r
        {\r
                short i;\r
-               for(i=600; i>=400; i--)\r
+               for(i=800; i>=400; i--)\r
                {\r
                        sound(i);\r
                }\r
                nosound();\r
        }\r
+       if(player.q == (TILEWH/SPEED)+1 && player.d > 0 && (player.triggerx == 5 && player.triggery == 5)){ player.hp--; }\r
+       //if(keyp(0x0E)) while(1){ if(xmsmalloc(24)) break; }\r
        }\r
 \r
        /* fade back to text mode */\r
@@ -325,20 +497,35 @@ void main() {
        modexPalBlack();\r
        modexLeave();\r
        setkb(0);\r
+       //system("mem /E /P");\r
        printf("Project 16 scroll.exe\n");\r
        printf("tx: %d\n", bg->tx);\r
        printf("ty: %d\n", bg->ty);\r
-       printf("player.x: %d\n", player.x);\r
-       printf("player.y: %d\n", player.y);\r
+       printf("player.x: %d", player.x);\r
+       if(player.hp==0) printf("%d wwww\n", player.y+8);\r
+       else printf("\nplayer.y: %d\n", player.y);\r
        printf("player.tx: %d\n", player.tx);\r
        printf("player.ty: %d\n", player.ty);\r
        printf("player.triggx: %d\n", player.triggerx);\r
        printf("player.triggy: %d\n", player.triggery);\r
+       printf("player.hp: %d\n", player.hp);\r
        printf("player.q: %d\n", player.q);\r
        printf("player.d: %d\n", player.d);\r
        printf("temporary player sprite 0: http://www.pixiv.net/member_illust.php?mode=medium&illust_id=45556867\n");\r
        printf("temporary player sprite 1: http://www.pixiv.net/member_illust.php?mode=medium&illust_id=44606385\n");\r
        printf("\n");\r
+       //xmsfree(&map);\r
+       //xmsfree(bg);\r
+       //xmsfree(spri);\r
+       //xmsfree(mask);\r
+       //xmsreport();\r
+       if(isEMS())\r
+       {\r
+               printf("%d\n", get_emem());\r
+               printf("%d\n", coretotalEMS());\r
+               dealloc_emem(emmhandle);\r
+               printf("%d\n", coretotalEMS());\r
+       }\r
        switch(detectcpu())\r
        {\r
                case 0: cpus = "8086/8088 or 186/88"; break;\r
@@ -359,24 +546,46 @@ allocMap(int w, int h) {
        result.width =w;\r
        result.height=h;\r
        result.data = malloc(sizeof(byte) * w * h);\r
+       //result.data = (byte *)alloc_emem(((int)sizeof(byte) * w * h)/1024);\r
+       /*if(isEMS() || checkEMS())\r
+       {\r
+               XMOVE mm;\r
+               //emmhandle = mallocEMS(coretotalEMS());//alloc_emem((int)sizeof(map))\r
+               mm.length=sizeof(result);\r
+               mm.sourceH=0;\r
+               mm.sourceOff=ptr2long(&result);\r
+               mm.destH=emmhandle;\r
+               mm.destOff=0;\r
+               ist = move_emem(&mm);\r
+               if(!ist){ dealloc_emem(emmhandle); exit(5); }\r
+               printf("%d\n", coretotalEMS());\r
+       }*/\r
 \r
        return result;\r
 }\r
 \r
-\r
 void\r
 initMap(map_t *map) {\r
        /* just a place holder to fill out an alternating pattern */\r
        int x, y;\r
        int i;\r
        int tile = 1;\r
-       map->tiles = malloc(sizeof(tiles_t));\r
+       //if(!isEMS() || !checkEMS())\r
+               map->tiles = malloc(sizeof(tiles_t));\r
+       //else\r
+       //      map->tiles = (tiles_t *)alloc_emem(sizeof(tiles_t));\r
 \r
        /* create the tile set */\r
-       map->tiles->data = malloc(sizeof(bitmap_t));\r
+       //if(!isEMS() || !checkEMS())\r
+               map->tiles->data = malloc(sizeof(bitmap_t));\r
+       //else\r
+       //      map->tiles->data = (bitmap_t *)alloc_emem(sizeof(bitmap_t));\r
        map->tiles->data->width = (TILEWH*2);\r
        map->tiles->data->height= TILEWH;\r
-       map->tiles->data->data = malloc((TILEWH*2)*TILEWH);\r
+       //if(!isEMS() || !checkEMS())\r
+               map->tiles->data->data = malloc((TILEWH*2)*TILEWH);\r
+       //else\r
+       //      map->tiles->data->data = (byte *)alloc_emem((TILEWH*2)*TILEWH);\r
        map->tiles->tileHeight = TILEWH;\r
        map->tiles->tileWidth =TILEWH;\r
        map->tiles->rows = 1;\r
@@ -529,11 +738,12 @@ mapDrawTile(tiles_t *t, word i, page_t *page, word x, word y) {
        word ry;\r
        rx = (i % t->cols) * t->tileWidth;\r
        ry = (i / t->cols) * t->tileHeight;\r
+       //mxPutTile(t->data, x, y, t->tileWidth, t->tileHeight);\r
        modexDrawBmpRegion(page, x, y, rx, ry, t->tileWidth, t->tileHeight, t->data);\r
 }\r
 \r
 \r
-void \r
+void\r
 mapDrawRow(map_view_t *mv, int tx, int ty, word y) {\r
        word x;\r
        int i;\r
@@ -549,7 +759,7 @@ mapDrawRow(map_view_t *mv, int tx, int ty, word y) {
        }\r
 }\r
 \r
-void \r
+void\r
 mapDrawCol(map_view_t *mv, int tx, int ty, word x) {\r
        int y;\r
        int i;\r
@@ -567,16 +777,15 @@ mapDrawCol(map_view_t *mv, int tx, int ty, word x) {
        i += mv->map->width;\r
        }\r
 }\r
-\r
 void\r
-animatePlayer(map_view_t *src, map_view_t *dest, /*map_view_t *top, */short d1, short d2, int x, int y, int ls, int lp, bitmap_t *bmp)\r
+animatePlayer(map_view_t *src, map_view_t *dest, /*map_view_t *top, */sword d, short scrolloffsetswitch, int x, int y, int ls, int lp, bitmap_t *bmp)\r
 {\r
-       short dire=32*d1; //direction\r
-       short qq; //scroll offset\r
+       sword dire=32*d; //direction\r
+       sword qq; //scroll offset\r
 \r
-       if(d2==0) qq = 0;\r
+       if(scrolloffsetswitch==0) qq = 0;\r
        else qq = ((lp)*SPEED);\r
-       switch (d1)\r
+       switch (d)\r
        {\r
                case 0:\r
                        //up\r
index 5b3d32c..ac939e5 100644 (file)
@@ -1,19 +1,46 @@
+#include <stdio.h>\r
 #include "src\lib\modex16.h"\r
+#include "src\lib\planar.h"\r
+#include "src\lib\bitmap.h"\r
 \r
 word far* clock= (word far*) 0x046C; /* 18.2hz clock */\r
 \r
 void main() {\r
+    bitmap_t bmp;\r
+    planar_buf_t *p;\r
+    word size;\r
     int i;\r
-    word start;\r
-    page_t page;\r
+    int plane;\r
+    int x,y;\r
+    byte color;\r
 \r
-    page=modexDefaultPage();\r
+    /* get the size we want */\r
+    printf("Width: ");\r
+    scanf("%d", &bmp.width);\r
+    printf("Height: ");\r
+    scanf("%d", &bmp.height);\r
+    printf("Color: ");\r
+    scanf("%x", &color);\r
 \r
-    modexEnter();\r
-    start = *clock;\r
-    for(i=0; i<500; i++) {\r
-       modexShowPage(&page);\r
+    /* allocate the bmp and fill it with 42 */\r
+    size = bmp.width * bmp.height;\r
+    bmp.data = malloc(size);\r
+    for(i=0; i<size; i++) {\r
+       bmp.data[i] = color;\r
     }\r
-    modexLeave();\r
 \r
+    /* create the planar buffer */\r
+    p = planar_buf_from_bitmap(&bmp);\r
+\r
+    /* print out the contents of each plane */\r
+    for(plane=0; plane < 4; plane++) {\r
+        i=0;\r
+       printf("Plane %d\n", plane);\r
+       for(y=0; y < p->height; y++) {\r
+           for(x=0; x < p->pwidth; x++) {\r
+               printf("%02X ", (int) p->plane[plane][i++]);\r
+           }\r
+           printf("\n");\r
+       }\r
+    }\r
 }\r
index 70d0176..2c0f710 100644 (file)
Binary files a/test.exe and b/test.exe differ
index ecbbf0e..20f665f 100644 (file)
Binary files a/test2.exe and b/test2.exe differ