/*

Last modified by Michael Kyritsis, Sunday 15h20

HexLib reads from hex.dat and writes to hex.out

The format of hex.dat is
byte // the size of the board, 1 byte
the contents of the board, row1-column1, then row1-column2... one byte each
with the value 1 for contestant hex, 2 for computer, 0 for no hex

The format of hex.out is exactly the same as hex.dat with data appended
The first byte appended is a 0 indicating no hex.dat found, or a 1
Then triples consisting of an identifier and 2 data bytes
where the identifier by may be:
0               GameIsOver, byte is the value it returned
1               MakeLibMove, byte one is the row, byte two is the column
//2             GetRow, byte is the row returned
//3             GetColumn, byte is the row returned
4               PutHex, first byte is the row, second the column
//5             GetMax, first byte is the return value
//6             LookAtBoard, first byte is either -1, or the row looked at, second = col

*/
#include "hexlib.h"

#define default_size 10
#define maxBoardSize 43
#define empty 0
#define competitor 1
#define marker 2
#define maxAssoc 5000
#define none 0
#define true 1
#define false 0
#define intelligence 3
// when no data file is present, play 1 intelligence move in 5

int returnRow = -1;
int returnColumn = -1;
int max = -1; // The current size of the board

typedef unsigned int word;
typedef unsigned char byte;
typedef char boolean;
typedef struct hextype{
	byte owned;
	word con;
	word garcon;
	};

void primopen(char *filename, int *handle, int *error);
void primclose(int handle, int *error);
void primread(int *bytes, char *buffer, int handle, int *error);
void primcreate(char *filename, int *error);
void primappend(int handle, int *error);
void primwrite(int *bytes, char *buffer, int handle, int *error);
void primprint(char *string);
void log (char* data, int bytes);

struct hextype board[maxBoardSize][maxBoardSize];
/* This is the big internal data structure which will be used
	Ignore element 0
	Always reference in the order [row][column]
	connectivity:
		rowA = rowB && colA = colB+1
		rowA = rowB+1 && colA = colB
		rowA = rowB+1 && colA = colB+1
*/

char signature;

typedef struct assoctype{
	byte lowest, highest;
	};

struct assoctype assoc[maxAssoc+1];
// Each hex belongs to a set. Adjacent hexes belong to the same set
word myset; // grows upwards in the assoc stack
word yourset; // grows downwards in the assoc stack

boolean found_file = false;
boolean last_move_was_contestant;
boolean initialized = false;
int my_height;  // THE HEIGHT OF THE SET WHICH INCLUDES THIS HEX
int your_height;
int my_garcon_height = 0;
int your_garcon_height = 0;
char logged[3];
int modulo=0;
// if no dat file is found then it plays a cleaver move every #intelligence
// moves, using modulo to count the moves made.

//--------------------------------------------------------------------------

void initialize (void)
{
	int any_error = 0;
	int i_random, j_random, l, i, j, handle, error, bytes;
	if (initialized)
		return;
	signature = 0;
	for (i=1; i<maxBoardSize; i++)
		for (j=1; j<maxBoardSize; j++)
			board[i][j].owned = none;
	// randomize();
	asm { mov si, 0x40
		mov es, si
		mov si, 0x6c
		mov ax, es:[si]
		xor bx, bx
		mov bl, al
		mov i_random, bx
		mov cl, 3
		shr ax, cl
		mov bl, al
		mov j_random, bx
		}
	i = i_random;
	j = j_random;
	for (l=1; l<=default_size; l++)
	{
		i%=default_size;
		if (i<0) i*=-1;
		i++;
		j%=default_size;
		if (j<0) j*=-1;;
		j++;
		board[i][j].owned = (l%2)+1;
		i+=i_random;
		j+=j_random;
	}
	max = default_size;
	primopen ("hex.dat", &handle, &error);
	any_error |= error;
	if (error == 0)
	{
		bytes = 1;
		primread (&bytes, (char*)&max, handle, &error);
		any_error |= error;
		for (i=1; i<=max; i++)
			for (j=1; j<=max; j++)
			{
				bytes = 1;
				primread (&bytes, (char*)&board[i][j].owned, handle, &error);
				any_error |= error;
			}
		primread (&bytes, &signature, handle, &error);
		any_error |= error;
		if (signature != 85)
			any_error |= 16;
		primclose (handle, &error);
		any_error |= error;
	}
	found_file = !any_error;
#ifndef NOLOG
	primcreate ("hex.out", &error);
	primopen ("hex.out", &handle, &error);
	bytes = 1;
	primwrite (&bytes, (char *)&found_file, handle, &error);
	bytes = 1;
	primwrite (&bytes, (char *)&max, handle, &error);
	for (i=1; i<=max; i++)
		for (j=1; j<=max; j++)
		{
			bytes = 1;
			primwrite (&bytes, (char *)&board[i][j].owned, handle, &error);
		}
	primclose (handle, &error);
#endif
	last_move_was_contestant = false;
	initialized = true;
}

//--------------------------------------------------------------------------

void log (char* data, int bytes)
{
#ifndef NOLOG
	int handle, error=0;
	primopen ("hex.out", &handle, &error);
	primappend (handle, &error);
	// if there is no data file found, then assume that the contestant
	// is trying out the library,
	// so don't write data to the log file, because we don't want
	// them to figure out the output - and write their own.
	if (!found_file)
		for (error=0; error<bytes; error++)
			data[error]=0;
	primwrite (&bytes, data, handle, &error);
	primclose (handle, &error);
#endif
}

//--------------------------------------------------------------------------

char SetNumberAssigned(void)
/* returns true if a set number was assigned to an element on the board,
	ie more calculations need to be done.*/
{
	byte row = 1;
	byte col;
	struct hextype *h;
	do {
		col = 1;
		do {
			h = &board[row][col];
			if (h->owned == marker || h->owned == competitor)
			{
				if (h->con == none)
				{
					if (h->owned == marker)
					{
						++myset;
						h->con = myset;
						assoc [h->con].lowest = row;
						assoc [h->con].highest = row;
					}
					else
					{
						--yourset;
						h->con = yourset;
						assoc [h->con].lowest = col;
						assoc [h->con].highest = col;
					}
					return 1;
				}
				if (h->garcon == none)
				{
					if (h->owned == marker)
					{
						++myset;
						h->garcon = myset;
						assoc [h->garcon].lowest = row;
						assoc[h->garcon].highest = row | 128; // 128 indicates garcon
					}
					else
					{
						--yourset;
						h->garcon = yourset;
						assoc [h->garcon].lowest = col;
						assoc [h->garcon].highest = col | 128;
					}
					return 1;
				}
			}
			++col;
		} while (col <= max);
		++row;
	} while (row <= max);
	return 0;
}

//--------------------------------------------------------------------------
/*
				___       ___            Col 0
			  /   \     /   \        _/
			 /     \___/     \     _/        Col -1
			 \ 11  /   \ 10  /   _/       _/
			  \___/     \___/  _/       _/
			  /   \  0  /   \         _/
		 ___/            \___/     \___   _/
		/        \  2  /   \  1  /   \
	  /       \___/     \___/     \
	  \  6  /       \ 12  /   \  7  /
		\___/    \___/     \___/  _
			 \       4  /     \  3  /       \_
			  \___/         \___/  _       \_
			  /     \  5  /   \   \_       \_
			 /               \___/     \    \_       \_
			 \  9  /   \  8  /      \_        Row 1
			  \___/     \___/         \_
												  Row 0
*/

const signed int Dr[] = {+1, +1, +0, +0, -1, -1, -1, +1, -1, -2, +2, +1, +0};
const signed int Dc[] = {+1, +0, +1, -1, +0, -1, +1, -1, -2, -1, +1, +2, +0};
/* You can now loop from 0 to 11 to visit all the neighbours of a hex */

//--------------------------------------------------------------------------

typedef char bridgeType[12];

void blockBridge (byte dir, bridgeType bridge)
/* hexes 6 to 11 may be reached by bridging */
{
	switch (dir)
	{
		case 0:
			bridge [10] = 0;
			bridge [11] = 0;
			break;
		case 1:
			bridge [10] = 0;
			bridge [7]      = 0;
			break;
		case 2:
			bridge [6]      = 0;
			bridge [11] = 0;
			break;
		case 3:
			bridge [7]      = 0;
			bridge [8]      = 0;
			break;
		case 4:
			bridge [9]      = 0;
			bridge [6]      = 0;
			break;
		case 5:
			bridge [8]      = 0;
			bridge [9]      = 0;
	}
}


//--------------------------------------------------------------------------

void findAssoc(void)
{
	char tempr, tempc;
	byte direction;
	byte row;
	byte col;
	char forward=1;
	bridgeType bridge;
	struct hextype *h;
	boolean change_made;
	char garcon;

	myset = 0;
	yourset = maxAssoc + 1;
	for (row = 1; row <= max; row++)
		for (col = 1; col <= max; col++)
			board [row][col].con = board [row][col].garcon = none;
	while (SetNumberAssigned())
	{
		do {
			change_made = false;
			forward *= -1;
			if (forward==1) row = 1; else row = max;
			for (; row >= 1 && row <= max; row+=forward)
			{ // row loop
				if (forward==1) col = 1; else col = max;
				for (; col >= 1 && col <= max; col+=forward)
				{ // col loop
					h = &board [row][col];
					if ((h->owned == marker || h->owned == competitor) &&
						(h->con == none || h->garcon == none))
					{  // find unsetted hex
						for (direction=0; direction<=11; direction++)
							bridge[direction] = true;
						for (direction=0; direction <=11; direction++)
						{ // direction loop
							tempr = row + Dr[direction];
							tempc = col + Dc[direction];
							// check for connection to other hexes:
							if (board[tempr][tempc].owned != none)
								blockBridge (direction, bridge);
							if (tempr >= 1 &&
								tempr <= max &&
								tempc >= 1 &&
								tempc <= max &&
								(direction <= 5 || bridge [direction]) &&
								h->owned == board [tempr][tempc].owned)
							{ // bounds checking
								if (h->con == none &&
									board [tempr][tempc].con != none &&
									direction <= 5)
								{
									change_made = true;
									h->con = board [tempr][tempc].con;
									if (h->owned == marker)
									{
										if (row < assoc [h->con].lowest)
											 assoc [h->con].lowest = row;
										if (row > assoc [h->con].highest)
											 assoc [h->con].highest = row;
									}
									else
									{
										if (col < assoc [h->con].lowest)
											 assoc [h->con].lowest = col;
										if (col > assoc [h->con].highest)
											 assoc [h->con].highest = col;
									}
								}
								if (h->garcon == none &&
									board [tempr][tempc].garcon != none)
								{
									change_made = true;
									h->garcon = board [tempr][tempc].garcon;
									if (h->owned == marker)
									{
										if (row < assoc [h->garcon].lowest)
											 assoc [h->garcon].lowest = row;
										if (row > (assoc [h->garcon].highest & 127))
											 assoc [h->garcon].highest = row | 128;
									}
									else
									{
										if (col < assoc [h->garcon].lowest)
											assoc [h->garcon].lowest = col;
										if (col > (assoc [h->garcon].highest & 127))
											assoc [h->garcon].highest = col | 128;
									}
								}
							} // end of bounds checking
						} // end of direction loop
					} // end of finding setted hex
				} // end of col loop
			} // end of row loop
		} while (change_made);
	} // end of setNumberAssigned loop

	// Now loop through the hexes and see if some are not the highest in a
	// set, or perhaps single hexes, and if they have a possible bridging
	// going upwards then decrease the assoc.lowest for that set.
	// For example a single hex should be considered to have a height of
	// 2 and not 0, which is what the previous block of code returns

	// start with stretching sets belonging to the marker
	for (row=2; row<max; row++)
		for (col=1; col<=max; col++)
		{
			if (row<=assoc[board[row][col].garcon].lowest&&
				board[row][col].owned==marker &&
				board[row-1][col].owned==none &&
				board[row-1][col-1].owned==none)
				assoc[board[row][col].garcon].lowest=row-1;
			garcon = assoc[board[row][col].garcon].highest&128;
			if (row>=(assoc[board[row][col].garcon].highest&127) &&
				board[row][col].owned==marker &&
				board[row+1][col].owned==none &&
				board[row+1][col+1].owned==none)
				assoc[board[row][col].garcon].highest=row+1|garcon;
		}
	// stretch sets belonging to the competitor
	for (row=1; row<=max; row++)
		for (col=2; col<max; col++)
		{
			if (col<=assoc[board[row][col].garcon].lowest&&
				board[row][col].owned==competitor &&
				board[row][col-1].owned==none &&
				board[row-1][col-1].owned==none)
				assoc[board[row][col].garcon].lowest=col-1;
			garcon = assoc[board[row][col].garcon].highest&128;
			if (col>=(assoc[board[row][col].garcon].highest&127) &&
				board[row][col].owned==competitor &&
				board[row][col+1].owned==none &&
				board[row+1][col+1].owned==none)
				assoc[board[row][col].garcon].highest=col+1|garcon;
		}
}

//--------------------------------------------------------------------------

boolean I_complete_already; // HAVE I ALREADY WON?
boolean you_complete_aready;
boolean I_have_garcon; // DO I HAVE A GUARANTEED CONNECTED SET?
boolean you_have_garcon;
boolean connected; // LOOPING VARIABLE
bridgeType bridge;

//--------------------------------------------------------------------------

void selectHex (
	int *my_gar_or_con_height,
	int *your_gar_or_con_height,
	int temp_row,
	int temp_col,
	int *row,
	int *col)
{
	int threshhold;
	int agro;

	if (my_height < *my_gar_or_con_height)
		my_height = *my_gar_or_con_height;
	if (your_height < *your_gar_or_con_height)
		your_height = *your_gar_or_con_height;
	if (your_garcon_height+1 == max)
		agro = 2;
	else
		agro = 3; // AGGRESSIVENESS
	if (my_height * 2 - *my_gar_or_con_height * 2 +
		your_height * agro - *your_gar_or_con_height * agro
		> 0)
	{
		*my_gar_or_con_height = my_height;
		*your_gar_or_con_height = your_height;
		*row = temp_row;
		*col = temp_col;
	}
}

//--------------------------------------------------------------------------

int NAME(GameIsOver) (void)
{
	struct assoctype *s;
	int my_con_height = 0;
	int your_con_height = 0;
	int set1;

	logged[0] = 0; // GameIsOver
	initialize ();
	findAssoc();
	for (set1=1; set1<=myset; set1++)
	{
		s = &assoc[set1];
		if (!(s->highest & 128)) // not interested in garcon
			if (s->highest - s->lowest > my_con_height)
				my_con_height = s->highest - s->lowest;
	}
	for (set1=yourset; set1<=maxAssoc; set1++)
	{
		s = &assoc[set1];
		if (!(s->highest & 128))
			if (s->highest - s->lowest > your_con_height)
				your_con_height = s->highest - s->lowest;
	}
	if (my_con_height+1 == max)
	{
		logged[1] = 3;
		log (logged, 3);
		return 3;// then I have won already
	}
	else if (your_con_height+1 == max)
	{
		logged[1] = 2;
		log(logged, 3);
		return 2;// then you have won already
	}
	else
	{
		logged[1] = 0;
		log (logged, 3);
		return 0;
	}
}

//--------------------------------------------------------------------------

void NAME(MakeLibMove(void))
/* calculate the next move and place the hex on the board. The change
to the board is indicated by LookAtBoard(), GetRow() and GetColumn() */
{
	int my_highest; // THE HIGHEST HEX (GAR)CON TO THIS HEX
	int your_highest;
	int my_lowest;
	int your_lowest;
	int my_con_height; // THE HEIGHT OF MY LONGEST SET
	int your_con_height;
	int my_garcon_height; // THE HEIGHT OF MY LONGEST GUARANTEED SET
	int your_garcon_height;
	word set1; // LOOPING VARIABLE
	byte dir;
	struct assoctype *s;
	struct hextype *h;
	int row, col, r1, c1;

	initialize ();
	logged[0] = 1; // MakeLibMove
	if (!last_move_was_contestant)
	{
		logged[1] = -1;
		log (logged, 3);
		return;
	}
	findAssoc();
	returnRow = -1; // UNDECIDED HEX
	returnColumn = -1;
	my_con_height = 0;
	my_garcon_height = 0;
	your_con_height = 0;
	your_garcon_height = 0;
	for (set1=1; set1<=myset; set1++)
	{
		s = &assoc[set1];
		if ((s->highest) & 128)
		{
			s->highest &= 127;
			if (s->highest - s->lowest > my_garcon_height)
				my_garcon_height = s->highest - s->lowest;
		}
		else
		{
			if (s->highest - s->lowest > my_con_height)
				my_con_height = s->highest - s->lowest;
		}
	}
	for (set1=yourset; set1<=maxAssoc; set1++)
	{
		s = &assoc[set1];
		if (s->highest & 128)
		{
			s->highest &= 127;
			if (s->highest - s->lowest > your_garcon_height)
				your_garcon_height = s->highest - s->lowest;
		}
		else
		{
			if (s->highest - s->lowest > your_con_height)
				your_con_height = s->highest - s->lowest;
		}
	}
	if (my_con_height+1 == max)
	{
		logged[1] = -1;
		log (logged, 3);
		return;// then I have won already
	}
	else
		if (your_con_height+1 == max)
		{
			logged[1] = -1;
			log (logged, 3);
			return;// then you have won already
		}
		else
		{
			I_have_garcon = my_garcon_height+1 == max;
			you_have_garcon = your_garcon_height+1 == max;
			for (row=1; row<=max; row++)
				for (col=1; col<=max; col++)
					if (board [row][col].owned == none)
					{
						// CONSIDER PLACING A HEX HERE
						my_highest = row;
						my_lowest = row;
						your_highest = col;
						your_lowest = col;
						for (dir=0; dir<=11; dir++)
							bridge [dir] = true;
						for (dir=0; dir<=11; dir++)
						{
							r1 = row + (int)Dr[dir];
							c1 = col + (int)Dc[dir];
							if (r1<=max && r1>=1 &&
								c1<=max && c1>=1 && // WITHIN LIMITS OF BOARD
								( dir<=5 || bridge [dir] ))
							{
								h = &board[r1][c1];
								if (h->owned == marker)
								{
									blockBridge (dir, bridge);
									if (I_have_garcon)
										if (dir <= 5)
											set1 = h->con;
										else
											set1 = none;
									else
										set1 = h->garcon;
									if (set1 != none)
									{
										if (assoc[set1].highest > my_highest)
											my_highest = assoc [set1].highest;
										if (assoc [set1].lowest < my_lowest)
											my_lowest = assoc [set1].lowest;
									}
								}
								else if (h->owned == competitor)
								{
									blockBridge (dir, bridge);
									if (I_have_garcon)
										if (dir <= 5)
											set1 = h->con;
										else
											set1 = none;
									else if (you_have_garcon)
										if (dir <= 5)
											set1 = h->con;
										else
											set1 = none;
									else
										set1 = h->garcon;
									if (set1 != none)
									{
										if (assoc [set1].highest > your_highest)
											your_highest = assoc [set1].highest;
										if (assoc [set1].lowest < your_lowest)
											your_lowest = assoc [set1].lowest;
									}
								}
							} // end of within limits of board
						} // end of for dir = 0 to 11
						my_height = my_highest - my_lowest;
						your_height = your_highest - your_lowest;
						if (I_have_garcon)
							selectHex (&my_con_height, &your_con_height, row, col, &returnRow, &returnColumn);
						else
							if (you_have_garcon)
								selectHex (&my_garcon_height, &your_con_height, row, col, &returnRow, &returnColumn);
							else
								selectHex (&my_garcon_height, &your_garcon_height, row, col, &returnRow, &returnColumn);
					} // end of if (board [row, column].owned == none)
		} // end of checking that
	modulo++;
	if (returnRow <= 0 || !found_file && modulo%intelligence)
	{
		row = returnRow;
		col = returnColumn;
		for (r1=1; r1<=max; r1++)
			for (c1=1; c1<=max; c1++)
				if (board[r1][c1].owned == none)
				{
					returnRow = r1;
					returnColumn = c1;
				}
		for (r1=1; r1<=10; r1++)
		{
			row = (row + returnRow) % max + 1;
			col = (col + returnColumn) % max + 1;
			if (board[row][col].owned == none)
			{
				returnRow = row;
				returnColumn = col;
			}
		}
	}
	board[returnRow][returnColumn].owned = marker;
	last_move_was_contestant = false;
	logged[1] = (char)returnRow;
	logged[2] = (char)returnColumn;
	log (logged, 3);
}

//--------------------------------------------------------------------------

int NAME(GetRow) ()
{
	initialize ();
//      logged[0] = 2; // GetRow
//      logged[1] = (char)returnRow;
//      log (logged, 3);
	return returnRow;
}

//--------------------------------------------------------------------------

int NAME(GetColumn) ()
{
	initialize ();
//      logged[0] = 3; // GetColumn
//      logged[1] = (char)returnColumn;
//      log (logged, 3);
	return returnColumn;
}

//--------------------------------------------------------------------------

void NAME(PutHex) (int row, int column)
{
	initialize ();
	if (last_move_was_contestant || row < 1 || column < 1 || row > max || column > max)
		return;
	if (board[row][column].owned != none)
		return;
	board[row][column].owned = competitor;
	last_move_was_contestant = true;
	logged[0] = 4; // PutHex
	logged[1] = (char)row;
	logged[2] = (char)column;
	log (logged, 3);
}

//--------------------------------------------------------------------------

int NAME(GetMax)()
{
	initialize ();
//      logged[0] = 5; // GetMax
//      logged[1] = (char)max;
//      log (logged, 3);
	return max;
}

//--------------------------------------------------------------------------

int NAME(LookAtBoard) (int row, int column)
{
	initialize ();
//      logged[0] = 6; // LookAtBoard
// logged[1] = (char)row;
//      logged[2] = (char)column;
	if (row < 1 || column < 1 || row > max || column > max)
	{
//              logged[1] = -1;
//              log (logged, 3);
		return -1;
	}

//      log (logged, 3);
	return board[row][column].owned;
}

//--------------------------------------------------------------------------
//--------------------------------------------------------------------------
//--------------------------------------------------------------------------

//--------------------------------------------------------------------------
// primopen: opens file...
//   error codes: 1 = invalid function
//                2 = file not found
//                3 = path not found
//                4 = no handles available
//                5 = access denied
//               12= invalid access code
//--------------------------------------------------------------------------
void primopen(char *filename, int *handle, int *error)
{ int localhandle;
  int localerror;
asm {
	mov ah, 3Dh       // open file
	mov al, 2             // 2 = read and write access
	mov dx, word ptr [filename]  // ds:dx = name - zero terminated string
	int 21h

	jc open_error
	mov localhandle,ax
	mov localerror,0
	jmp proc_end
	}
open_error:
asm {
	mov localerror,ax
}
proc_end:
	*handle = localhandle;
	*error = localerror;
}

//--------------------------------------------------------------------------
// primcreate: creates a file
//              truncates it if it exists already
//--------------------------------------------------------------------------
void primcreate(char *filename, int *error)
{ int localerror;
asm {
	mov ah, 3Ch       // create file
	mov cx, 0             // file attribute is normal
	mov dx, word ptr [filename]  // ds:dx = name - zero terminated string
	int 21h

	jc create_error
	mov localerror,0
	jmp proc_end
	}
create_error:
asm {
	mov localerror,ax
}
proc_end:
	*error = localerror;
}

//-------------------------------------------------------------------------
//      close (int handle, int *error)
//      error code: 6 = invalid handle
//-------------------------------------------------------------------------

void primclose(int handle, int *error)
{ int localerror;
asm {
	mov ah, 3Eh       // close file
	mov bx, handle
	int 21h

	jnc close_successful

	mov localerror,ax
}
close_successful:
asm {
	mov localerror,0
}
	*error = localerror;
}

//-------------------------------------------------------------------------
// primwrite (int *bytes, char *buffer, int handle, int *error)
//     error codes: 5 = access denied
//                  6 = invalid handle
//-------------------------------------------------------------------------

void primwrite(int *bytes, char *buffer, int handle, int *error)
{ int localbytes;
  int localerror;
  localbytes = *bytes;
  localerror = *error;
asm {
	mov ah, 40h                     // read file or device
	mov cx, localbytes
	mov dx, word ptr [buffer]       // ds:dx = pointer to buffer
	mov bx, handle
	int 21h

	jc write_error
	mov ax, localbytes        // number of bytes successfully written
	mov localerror,0
	jmp proc_end
}
write_error:
asm {
	mov localerror,ax
}
proc_end:
	*bytes = localbytes;
	*error = localerror;
}

//-------------------------------------------------------------------------
// primappend (int handle, int *error)
//     error codes: 5 = access denied
//                  6 = invalid handle
//-------------------------------------------------------------------------

void primappend(int handle, int *error)
{ int localerror;
  localerror = *error;
asm {
	mov ah, 42h                     // move file pointer
	mov al, 2                       // move relative to end of file
	mov cx, 0                       // high order word of distance to move
	mov dx, 0                       // low order word of distance to move
	mov bx, handle
	int 21h

	jc append_error
	mov localerror,0
	jmp proc_end
}
append_error:
asm {
	mov localerror,ax
}
proc_end:
	*error = localerror;
}

//-------------------------------------------------------------------------
//      read (int *bytes, char *buffer, int handle, int *error)
//     error codes: 5 = access denied
//                  6 = invalid handle
//-------------------------------------------------------------------------

void primread(int *bytes, char *buffer, int handle, int *error)
{ int localbytes;
  int localerror;
  localbytes = *bytes;
  localerror = *error;
asm {
	mov ah, 3Fh                     // read file or device
	mov cx, localbytes
	mov dx, word ptr [buffer]       // ds:dx = pointer to buffer
	mov bx, handle
	int 21h

	jc read_error
	mov localbytes, ax              // number of bytes successfully read
	mov localerror,0
	jmp proc_end
}
read_error:
asm {
	mov localerror,ax
}
proc_end:
	*error = localerror;
	if (localbytes < *bytes) // if fewer bytes were read than requested
		*error |= 8;
	*bytes = localbytes;
}

//-------------------------------------------------------------------------
//      print (char *string)
//              string is terminated by '$'
//-------------------------------------------------------------------------

void primprint(char *string)
{
	asm {
		mov ah, 9h              // display string
		mov dx, word ptr [string]
		int 21h
		}
}
