home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Collection of Hack-Phreak Scene Programs
/
cleanhpvac.zip
/
cleanhpvac
/
OTHELLO.ZIP
/
othello.c
Wrap
Internet Message Format
|
1995-04-15
|
45KB
From: bet@std.sbi.com (Bennett Todd)
Newsgroups: alt.sources
Subject: Othello.c
Date: 22 Jun 1994 03:23:45 GMT
Organization: Salomon Brothers, Inc.
This is a fairly portable program I wrote to play othello for an AI class
back in 1983. I wrote it using DeSmet C, and it has some special PC
optimizations, under #ifdef DeSmet, that are non-portable. You'll need to
blow out the #define DeSmet if you aren't using his C compiler. If your C
compiler gets annoyed at illegal syntax within #if/#endif that isn't being
compiled, then you'll also need to edit out the blocks that are ifdef
DeSmet.
This program uses Alpha-Beta pruning of a minimax lookahead tree. It has a
fairly tweaked board evaluation heuristic, with an edge strategy and so on.
It is possibly over-optimized by modern standards, but this thing plays a
strong game, on a 4.77MHz 8088 in 64K of RAM, with no move taking more than
30 seconds.
-Bennett
bet@sbi.com
/*
* Othello playing program. Fairly portable if DeSmet isn't defined;
* defining DeSmet enables some inline-assembler screen wizzery on PCs,
* using DeSmet C's convention for inline assembler.
*
* The code looks a _lot_ better if you display it with tabstops every
* four spaces, rather than every 8.
*
* Written in 1993 by Bennett Todd, with some help by John White, who
* designed the finite state automaton for edge strategy.
*/
/*
Plays othello using alpha-beta search.
Defining DEBUG_LEVEL as 1 sets enhanced error trapping, and
2 gives run-time statistics
3 enables enhanced run-time sanity checking
defining IBMPC generates non-portable PC optimization
defining DeSmet changes handling of '\n' on input
Switches:
-f gofirst (default=no)
-bn allocate n boards: default=64, grows as branching factor*depth
-dn look ahead n moves: default is variable, must be greater than 0
-rn reverse the initial board in various ways:
-r1 reverse initial board setup; 'o' still goes first
-r2 reverse initial board setup; 'x' goes first
-r3 normal initial board setup; 'x' goes first
*/
#include <stdio.h>
#define DEBUG_LEVEL 1
#define DeSmet
/*
About the following defines:
BEST and WORST are upper and lower bounds, respectively, on the
worth of a board.
MAXSONS is the maximum number of possible moves from any given board,
used to size the sons array in a board node.
EMPTY, MINE, and HIS are values for the possible states of a square
on an othello board. In the original code, they were used as an
enumerated type, after the fashion of PASCAL. However, during
the process of optimizing the program, It was observed that
in the code
<expression>!=EMPTY
!=EMPTY doesn't change the logical value of the expression, and
<expression>==EMPTY is equivalent to !<expression>
Thus the contents of board cells are sometimes used as boolean
variables.
What's worse, many nested if expressions and switch statements
were removed by using values of board cells as subscripts into
arrays, a process which altogether exceeded any reasonable bounds
in the finite state machine found in the function worth.
*/
#define TRUE 1
#define FALSE 0
#define WORST -1000
#define BEST 1000
#define MAXSONS 30
#define EMPTY '\0'
#define MINE '\1'
#define HIS '\2'
typedef struct boardtype
{
struct boardtype *sons[MAXSONS]; /* pointers to descendants */
int val; /* worth of board position */
char *array[8]; /* the board itself */
}
BOARD;
BOARD *freeboards; /* pointer to head of linked list of free boards */
int maxdepth[60]= /* maxdepth[movenumber] is the depth that alphabeta */
{ /* will search on the movenumber'th move */
1, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 4, 7, 6, 5, 4, 3, 2, 1, 1
},
maxboards=64, /* default number of boards to allocate */
reversed=FALSE, /* switch to reverse initial board position */
movenumber=0, /* counter for what move we are on */
time_out[3]= /* timeout points, to hurry up if we are in */
{ /* danger of overshooting 30 seconds */
2000,2500,2800
},
start_dive=52, /* the movenumber on which to switch worths */
gofirst=FALSE, /* by default, the opponent goes first */
max, /* who is playing at any given instant */
(*worth)(), /* pointer to the current worth function */
i_tran[64]= /* i subscript generator */
{
0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1,
2, 2, 2, 2, 2, 2, 2, 2,
3, 3, 3, 3, 3, 3, 3, 3,
4, 4, 4, 4, 4, 4, 4, 4,
5, 5, 5, 5, 5, 5, 5, 5,
6, 6, 6, 6, 6, 6, 6, 6,
7, 7, 7, 7, 7, 7, 7, 7
},
j_tran[64]= /* j subscript generator */
{
0, 1, 2, 3, 4, 5, 6, 7,
0, 1, 2, 3, 4, 5, 6, 7,
0, 1, 2, 3, 4, 5, 6, 7,
0, 1, 2, 3, 4, 5, 6, 7,
0, 1, 2, 3, 4, 5, 6, 7,
0, 1, 2, 3, 4, 5, 6, 7,
0, 1, 2, 3, 4, 5, 6, 7,
0, 1, 2, 3, 4, 5, 6, 7
};
long time; /* time tick holder, for stopwatch */
/*
The array moves, declared below, is an array of pointers to arrays
of pointers to arrays of subscripts, ranging from 0 to 63.
A board is declared in the structure declaration above to be represented
by an array of 8 pointers to arrays of chars (8 per array). In my
initialization of the board structures, I explicitly allocate the row
arrays consecutively; therefore, board->array[0] can be taken as a
pointer to an array 64 long of chars, containing the cells of the board.
board->array[0][moves[i][j][k]] is the k-th step in th j-th direction
from the i-th cell in board. Moves is 64 long; moves[i] is variable
length (one for each direction in which a move is possible) delimited
with NULLs, and move[i][j] is variable length, delimited by i. In
other words, to walk as far as possible in the j-th direction from
cell i, you could use
for(k=0;i!=moves[i][j][k];k++)
though I didn't, since I use pointers to walk through the array.
*/
char **moves[64],
print_chars[3]={' ','x','o'}; /* the character to print for a cell */
#if DEBUG_LEVEL > 1
int allsons_called,
worth_called,
max_used_boards,
boards_in_use, /* various variables to accumulate statistics */
greatest_branching,
alpha_prunes,
beta_prunes;
#endif
#if DEBUG_LEVEL > 2
int moves_checksum,
board_count=0;
BOARD *board_bottom,
*board_end;
#endif
#ifdef IBMPC
int line_number, /* the current screen line number */
screen_seg; /* segment offset for video ram */
char scr_line[160]; /* output line--character data */
/* alternating with attribute bytes */
#if DEBUG_LEVEL > 1
#define BOARD_TOP 960
#else
#define BOARD_TOP 1120
#endif
#endif
main(argc,argv)
int argc;
char **argv;
{
int i,
temp,
worth_1(), /* necessary declarations to tell the compiler what */
worth_2(), /* kind of objects these are, for assignment to worth */
oldmove;
long time_tick(); /* only implemented on the IBM-PC, a stopwatch */
BOARD *root, /* the root of the current tree, changed by */
*firstboard(); /* both move() and getmove() */
#if DEBUG_LEVEL > 1
#ifdef IBMPC
char *_showsp(),
*_memory();
#endif
#endif
/* parse the switches on the command line */
for(i=1;i<argc;i++)
/* all of which start with '-' */
if(*argv[i]=='-')
switch(*++argv[i])
{
case 'f': /* set a boolean switch gofirst, and */
case 'F': /* exchange how squares are printed */
gofirst=TRUE;
temp=print_chars[1];
print_chars[1]=print_chars[2];
print_chars[2]=temp;
break;
case 'd': /* force depth to be maxdepth for every move */
case 'D':
if(isdigit(*++argv[i]))
{
maxdepth[0]=atoi(argv[i]);
start_dive=60-maxdepth[0];
for(temp=0;temp<59;temp++)
maxdepth[temp+1]=maxdepth[temp];
}
else
fprintf(stderr,"bad depth %s\n",argv[i]);
break;
case 'b': /* change number of boards to allocate */
case 'B':
if(isdigit(*++argv[i]))
maxboards=atoi(argv[i]);
else
fprintf(stderr,"bad board limit %s\n",argv[i]);
break;
case 'r': /* reverse board position in various ways */
case 'R':
if(isdigit(*++argv[i]))
{
temp=atoi(argv[i]);
if(temp%2)
reversed=TRUE;
if(temp>1)
{
temp=print_chars[1];
print_chars[1]=print_chars[2];
print_chars[2]=temp;
}
}
else
fprintf(stderr,"bad reverse switch %s\n",argv[i]);
break;
default:
fprintf(stderr,"unknown switch %s ignored\n",argv[i]);
break;
}
else
fprintf(stderr,"unknown arg %s ignored\n",argv[i]);
#ifdef IBMPC
init_screen(); /* initialize scr_line and screen_seg */
#endif
/* call various initialization routines */
worth=worth_1; /* the main heuristic function */
#if DEBUG_LEVEL > 1
#ifdef IBMPC
printf("freespace bottom %u\n",((unsigned int) _memory()));
printf(" stack top %u\n",((unsigned int) _showsp()));
#endif
#endif
initmoves(); /* initialize the array moves */
#if DEBUG_LEVEL > 2
board_bottom=freeboards;
moves_checksum=check_moves();
#endif
initfree(); /* set up free boards list */
#if DEBUG_LEVEL > 2
printf("allocated %d boards\n",board_count);
#endif
root=firstboard(); /* set up to play the game */
#if DEBUG_LEVEL > 2
board_count--;
printf("which leaves %d to play with.\n",board_count);
#endif
time=time_tick(); /* and mark time */
if(gofirst)
move(&root);
else
{
#ifdef IBMPC
clear_home(); /* fast clear screen */
#else
putchar('\f'); /* slow one */
#endif
putboard(root,NULL); /* put only one board */
}
oldmove=(-1); /* dummy out endgame flag, it's only starting now! */
/* main game loop */
while(movenumber<60 && movenumber!=oldmove) /* game lasts 60 moves, */
{ /* unless no one can move */
#if DEBUG_LEVEL > 2
if(moves_checksum!=check_moves())
{
printf("Moves checksum failed. Sorry, boss.\n");
exit();
}
if(board_count!=count_boards())
printf("I seem to have mislain a board: %d/%d\n",
board_count,count_boards());
#endif
if(movenumber>start_dive) /* change to h*, the "true" h function */
{
time_out[0]=time_out[1];
worth=worth_2; /* only computable by looking to the end*/
}
oldmove=movenumber; /* set to recognize if no one can move */
getmove(&root); /* get a move from the user */
move(&root); /* make a move */
}
score(root); /* report the result */
}
/*
move makes a move on the (called-by-reference) board. It first
expands all possible moves with a call to allsons, with max set to
true (moves is trying to maximize the evaluation function), then uses
alphabeta to evaluate each possibility, picking the best.
Move is different from alphabeta mostly in that
a) it always and only plays as max
b) it must keep track of the actual boards, and not just
their values (alphabeta always reclaims as soon as possible)
c) it is in charge of initializing everything for the search
d) rather than returning a backed up value, it simply outputs the
move (and some other junk) and resets the root to point to the
new move.
*/
move(board)
BOARD **board;
{
BOARD *tempboard;
int i,
n,
rush=0, /* used to panic stop short of 30 seconds */
temp,
alpha=WORST, /* the root is max, and therefore has an alpha value*/
best=WORST-1, /* must be worst than the worst possible value, so */
/* that a move will be picked even if they are all */
/* the worst. */
bestboard; /* subscript of the best board seen so far */
char *t1, /* a couple of temporary pointers used to */
*t2; /* figure out exactly where I moved. */
long time_tick(); /* stopwatch function, implemented only on the PC */
max=TRUE; /* let us get this straight, once and for all... */
#ifdef IBMPC
clear_home(); /* fast clear screen */
#else
putchar('\f'); /* slow one */
#endif
/* read the following as "if( <number of sons found> == 0)" */
if(!(n=allsons(*board))) /* "==0" is equivalent to "!" */
{
printf("I cannot move. Your turn.\n");
putboard(*board,NULL); /* put the board so that he can see the */
return; /* result of his last move */
}
#if DEBUG_LEVEL > 1
allsons_called=1;
worth_called=n;
boards_in_use=max_used_boards=1; /* initialize variables to */
greatest_branching=n; /* accumulate statistics */
alpha_prunes=0;
beta_prunes=0;
#endif
/* for every son */
for(i=0;i<n;i++)
{
max=FALSE; /* think as my opponent would think */
/* if this is better than the best we have seen so far */
if(best<(temp=alphabeta((*board)->sons[i],maxdepth[movenumber],
alpha,BEST)))
{
best=alpha=temp;
bestboard=i;
}
/* make sure we do not under any circumstances exceed 30 seconds */
if((time_tick()-time) > time_out[rush])
{
if(rush==2)
#if DEBUG_LEVEL > 1
{
line_number=0;
fast_puts("buggering out, on account of lack of time!!");
#endif
goto outta_here;
#if DEBUG_LEVEL > 1
}
printf("I'm hurrying...\n");
#endif
rush++;
maxdepth[movenumber]--;
}
}
outta_here:
t1=(*board)->array[0]-1; /* set up pointers */
t2=(*board)->sons[bestboard]->array[0]-1; /* for comparison */
loop:
while(*++t1==(*++t2)); /* find different squares */
if(*t1) /* then this square was flipped, not actually moved */
goto loop; /* so keep trying */
/* temp gets the 0-63 subscript of the move */
temp=t1-(*board)->array[0];
#if DEBUG_LEVEL > 1
printf("I moved at %c %d in %5.2f seconds\n",
'a'+j_tran[temp],1+i_tran[temp],((float)(time_tick()-time))/100);
#else
if((time_tick()-time) < 2990)
printf("I moved at %c %d in %5.2f seconds\n",
'a'+j_tran[temp],1+i_tran[temp],((float)(time_tick()-time))/100);
else
printf("I moved at %c %d in 29.52 seconds\n",
'a'+j_tran[temp],1+i_tran[temp]);
#endif
putboard((*board),(*board)->sons[bestboard]);
#if DEBUG_LEVEL > 1
if((time_tick()-time) > 3000)
printf("\7\7\7\7\7\7");
printf("movenumber: %d choices: %d depth: %d",
movenumber,n,maxdepth[movenumber]);
#if DEBUG_LEVEL > 2
printf(" boards available: %d",count_boards()+n);
#endif
printf("\nallsons called: %d worth called: %d boards used: %d\n",
allsons_called,worth_called,max_used_boards);
printf(" alpha prunes: %d beta prunes: %d total: %d",
alpha_prunes,beta_prunes,alpha_prunes+beta_prunes);
printf(" max branching: %d\n",greatest_branching);
#endif
/* now reclaim unneeded boards, reset the root, and count this move */
for(i=0;i<n;i++)
if(i!=bestboard)
reclaim((*board)->sons[i]);
tempboard=(*board)->sons[bestboard];
reclaim(*board);
*board=tempboard;
movenumber++;
}
/*
alphabeta takes four parameters: the board to evaluate,
the depth to search, and the current alpha and beta values.
It returns the worth of the board, obtained by backing up
the values seen at the bottom of the search.
It plays maximizing or minimizing, based on the global
boolean variable max.
*/
int alphabeta(current,depth,alpha,beta)
BOARD *current;
int depth,
alpha,
beta;
{
int i,
n,
temp,
best,
curmax; /* contains the current value of the global "max", */
/* negated (to minimize the number of negations) */
BOARD *curson; /* contains the son being examined currently */
/* if no sons can be found */
if(!(n=allsons(current)))
{
max=!max; /* then try as the other guy */
if(!(n=allsons(current))) /* and if he can't move */
return(worth_2(current)); /* return the final value */
}
best=max?WORST:BEST; /* start off assuming the worst */
depth--; /* keep track of depth */
curmax=!max; /* and max through recursive calls */
/* for every son */
for(i=0;i<n;i++)
{
max=curmax;
curson=current->sons[i];
/*
This statement does it all. Recurse, propogating correct alpha
and beta values (only one of which can change at any given node)
unless of course the recursion has terminated, in which case we
use the value of the node, as evaluated by worth when allsons
created the node. Put the value thus obtained into temp, and
compare it with the best value seen so far. The sense of
comparison is reversed if curmax is true (bitwise xor is all
right if both booleans are "pure"--0 or 1).
*/
if(curmax^best<(temp=depth?
(curmax?
alphabeta(curson,depth,alpha,best)
:alphabeta(curson,depth,best,beta))
:curson->val))
{
/* check for an alphabeta "prune" of the tree */
if(curmax?(temp<=alpha):(temp>=beta))
{
#if DEBUG_LEVEL > 1
if(curmax)
beta_prunes++;
else /* accumulate statistics */
alpha_prunes++;
#endif
while(i<n) /* clean up */
reclaim(current->sons[i++]);
return(temp); /* and pack it in */
}
else
best=temp; /* remember the best value seen so far */
}
reclaim(curson);
}
return(best);
}
/*
Simple-minded free space management. Keep a bunch of boards on a linked
list. When one is needed, take it off the top. When no longer needed,
insert it at the top. Optionally, check for out-of-boards (The converse,
attempting to check for freeing of space not taken from the freeboard
list, seemed to be too difficult. Perhaps I could have kept around high-
water and low-water mark pointers to the space of legal board pointers.),
and accumulate statistics.
*/
BOARD *newboard()
{
BOARD *temp;
#if DEBUG_LEVEL > 0
if(!freeboards)
#if DEBUG_LEVEL > 1
{
printf("Gotta grab more boards...\n");
printf("Current boards outstanding : %d\n",boards_in_use);
printf(" High-water mark : %d\n",max_used_boards);
#endif
initfree();
#if DEBUG_LEVEL > 1
}
boards_in_use++;
if(max_used_boards<boards_in_use)
max_used_boards=boards_in_use;
#endif
#endif
temp=freeboards;
freeboards=freeboards->sons[0];
temp->sons[0]=NULL;
return(temp);
}
reclaim(board)
BOARD *board;
{
#if DEBUG_LEVEL > 1
boards_in_use--;
#endif
#if DEBUG_LEVEL > 2
if(board<board_bottom || board > board_end)
printf("warning: attempted to reclaim non-board\n");
else
{
#endif
board->sons[0]=freeboards;
freeboards=board;
#if DEBUG_LEVEL > 2
}
#endif
}
/*
Shell sort taken from K&R, sorts the n boards in the array into ascending
or descending order based on the global boolean max, sorting on the
values contained in the value member of each board structure. This sort
focuses the alphabeta search significantly, increasing the pruning enough
to cut depth 4 search time by approximately 65 percent.
*/
sort(boards,n)
BOARD **boards;
int n;
{
int i,
j,
gap,
jpg; /* temporary storage hold j+gap */
BOARD *tempboard;
for(gap=n/2;gap>0;gap/=2)
for(i=gap;i<n;i++)
for(j=i-gap;j>=0 &&
(max?(boards[j]->val<boards[jpg=j+gap]->val):
(boards[j]->val>boards[jpg=j+gap]->val));j-=gap)
{
tempboard=boards[j];
boards[j]=boards[jpg];
boards[jpg]=tempboard;
}
}
/*
Initialize a linked list of free boards. Each board has an array of
8 pointers to rows of 8 bytes each. Allocate space for the rows, and
initialize the pointer arrays to point to the rows. The rows in a board
are explicitly allocated contiguously, so it is possible (and turns out
to enhance efficiency at the expense of comprehensability) to treat
a board as a single array of 64 bytes, pointed to by the pointer to
the first row. Optionally, produce statistics on allocation of memory.
*/
initfree()
{
int i,j;
char *calloc(),
*boardspace;
freeboards=((BOARD *) calloc(maxboards,sizeof(BOARD)));
if(!freeboards)
{
printf("The board structure allocation failed. Sorry, boss.\n");
exit(0);
}
boardspace=calloc(maxboards,sizeof(char [64]));
if(!boardspace)
{
printf("The board freespace allocation failed. Sorry, boss.\n");
exit(0);
}
#if DEBUG_LEVEL > 1
printf(" board space %u\n",((unsigned int) freeboards));
printf(" thru %u\n",
((unsigned int) (freeboards+maxboards)));
printf(" board arrays %u\n",((unsigned int) boardspace));
printf(" thru %u\n",
((unsigned int) (boardspace+64*maxboards)));
#endif
/* thread linked list and arrange the row pointer arrays */
for(i=0;i<maxboards;i++)
{
freeboards[i-1].sons[0]=freeboards+i;
for(j=0;j<8;j++)
freeboards[i].array[j]=boardspace+64*i+8*j;
}
freeboards[maxboards-1].sons[0]=NULL;
#if DEBUG_LEVEL > 2
board_end=freeboards+(maxboards-1);
board_count+=maxboards;
#endif
/* We might be called again, if he needs more boards */
maxboards=10;
}
/*
The array moves contains information about the shape of a board, in
a fashion which simplifies the checking of loop conditions in the
code which is examining a board for a move, and flipping the resulting
pieces. Specifically, moves (which is a byte array because it needs
no large numbers) is used as follows:
moves[i][j][k] refers to the subscript of the
k-th square, in the
j-th direction, from the
i-th square on the board.
Moves is 64 long; moves[i] is variable length delimited by NULL; and
moves[i][j] is variable length delimited by the value of i.
*/
initmoves()
{
int i,
j,
k,
l,
m,
n;
char *calloc(),
**pointers,
*bytes;
/* 484 and 2016 are computed by rote */
pointers=((char **) calloc(484,sizeof(char **)));
if(pointers==NULL)
{
printf("initmoves: cannot allocate pointers. Sorry, boss.\n");
exit(0);
}
bytes=calloc(2016,sizeof(char));
if(bytes==NULL)
{
printf("initmoves: cannot allocate bytes. Sorry, boss.\n");
exit(0);
}
#if DEBUG_LEVEL > 1
printf("pointers set to %u\n",((unsigned int) pointers));
printf(" bytes set to %u\n",((unsigned int) bytes));
#endif
/* for each square on the board */
for(i=0;i<8;i++) for(j=0;j<8;j++)
{
/* set the corresponding entry of moves to some free pointers */
moves[i*8+j]=pointers;
/* for each direction */
for(k=(-1);k<2;k++) for(l=(-1);l<2;l++)
if(k || l) /* if neither k or l we aren't going anywhere */
{
*pointers=bytes; /* point to some free bytes */
/* let m and n walk to the edge of the board */
for(m=i+k,n=j+l;m>=0 && m<8 && n>=0 && n<8;m+=k,n+=l)
(*bytes++)=m*8+n;
if(m!=i+k || n!=j+l)
/* then we managed to walk somewhere */
{
(*bytes++)=i*8+j; /* terminate bytes list */
pointers++; /* get pointer for next direction */
}
}
(*pointers++)=NULL; /* terminate pointers list */
}
#if DEBUG_LEVEL > 1
printf("pointers left at %u\n",((unsigned int) pointers));
printf(" bytes left at %u\n",((unsigned int) bytes));
#endif
}
#if DEBUG_LEVEL > 2
int check_moves()
{
int i,
chk1,
chk2;
char **ptr1,
*ptr2;
chk1=chk2=0;
for(i=0;i<64;i++)
for(ptr1=moves[i];*ptr1;ptr1++)
for(ptr2=(*ptr1);(*ptr2)!=i;ptr2++)
{
if(*ptr2<0 || *ptr2>63)
printf("moves subscript out of range at i=%d sub=%d\n\7",i,*ptr2);
chk2+=chk1^=(*ptr2);
}
return(chk1+chk2);
}
int count_boards()
{
int i;
BOARD *ptr;
for(i=0,ptr=freeboards;ptr;i++,ptr=ptr->sons[0]);
return(i);
}
#endif
/*
allsons finds all sons for the board which is its parameter, sets the
array of pointers to sons to point to new boards containing the resulting
boards, sets the val member of each son board structure to the value as
evaluated by worth, and sorts the sons in order, to focus the alphabeta
search. It returns the number of sons it found.
*/
int allsons(pos)
BOARD *pos;
{
int cur=0, /* son next to allocate */
i;
char mine, /* mine, from the point of the current player */
hisn, /* likewise */
whose, /* temporary variable--keep from recomputing */
*board, /* pointer into the board array */
***move_ptr, /* pointer into the moves array */
**dir_ptr, /* pointer into moves[i] arrays of directions */
*sub_ptr; /* pointer into moves[i][j] arrays of subscrtips*/
BOARD *resultof(), /* create a board resulting from making a move */
*curson; /* point to current son */
#if DEBUG_LEVEL > 1
allsons_called++;
#endif
mine=max?MINE:HIS;
hisn=max?HIS:MINE;
board=pos->array[0];
/* for(i=0;i<64;i++) with move_ptr=moves[i] */
for(i=0,move_ptr=moves;i<64;i++,move_ptr++)
{
/* if(board[i]==EMPTY) */
if(!board[i])
/* for(j=0;moves[i][j]!=NULL;j++) with sub_ptr=moves[i][j] */
for(dir_ptr=(*move_ptr);sub_ptr=(*dir_ptr++);)
/* if he owns the cell in the j-th direction */
if(board[*sub_ptr++]==hisn)
{
/* scan until edge of board or end of list */
/*
NOTE: moves[i][j] is delimited by i, so the edge of
the board looks like a cell containing the same thing
as board[i], which must be empty if I got into this
code; therefore, hitting edge of board looks just
like seeing a space at the end of the string of
opponents pieces, which means I cannot capture them.
*/
while((whose=board[*sub_ptr++])==hisn);
if(whose==mine) /* then we have a possible capture */
{
curson=pos->sons[cur++]=resultof(pos,i);
curson->val=(*worth)(curson);
goto endit; /* don't look in other directions */
}
}
endit: ;
}
#if DEBUG_LEVEL > 0
if(cur>MAXSONS)
{
fprintf(stderr,"allsons: I needed %d sons for",cur+1);
putboard(pos,NULL);
fprintf(stderr,"allsons: but I only am alotted %d.\n",MAXSONS);
printf("Sorry, boss.\n");
exit(0);
}
#endif
#if DEBUG_LEVEL > 1
if(greatest_branching < cur)
greatest_branching=cur;
#endif
sort(pos->sons,cur);
pos->sons[cur]=0;
return(cur);
}
/*
Resultof makes a copy of the board (using _move(), a fast block
move routine that comes with Mark DeSmet's Cware C compiler, if
the code is being generated for an IBM-PC) and flips all squares
thet need to be flipped based on making a move at position x
(where x ranges for 0 to 63), takes the square at x, and returns
a pointer to the resulting board. It calls newboard(), the free
board server.
*/
BOARD *resultof(father,x)
BOARD *father;
int x;
{
int mine, /* mine, from the point of view of the current player */
hisn; /* likewise */
char *board, /* pointer into the board array */
#ifndef IBMPC
*tmpptr, /* pointers for copying the board in portble C */
*tmplim,
#endif
**dir, /* pointer for moves[x][j], a direction */
*sub; /* pointer to a subscript, moves[i][j][k] */
BOARD *newboard(),
*temp;
mine=max?MINE:HIS;
hisn=max?HIS:MINE;
temp=newboard();
/* Copy the board. Use a block move on the PC */
#ifdef IBMPC
_move(64,father->array[0],temp->array[0]);
#else
board=temp->array[0];
tmpptr=father->array[0];
tmplim=board+64;
while(board<tmplim)
(*board++)=(*tmpptr++);
#endif
board=temp->array[0];
/* for(j=0;moves[x][j]!=NULL;j++) */
for(dir=moves[x];*dir;dir++)
/* if the cell in the j-th direction is his */
if(board[**dir]==hisn)
{
sub=(*dir);
/* scan as long as the pieces are his */
/* (Please see discussion in allsons */
while(board[*++sub]==hisn);
/* if the search was terminated by a piece of mine */
if(board[*sub]==mine)
{
/* do the same scan, flipping pieces as you go */
sub=(*dir);
while(board[*sub]==hisn)
board[*(sub++)]=mine;
}
}
/* put a piece where we actually moved */
board[x]=mine;
return(temp);
}
/*
firstboard() returns a pointer to a board set up in the initial position.
It is the only routine that actually zeros out a board array, and sets
things up in it. The remainder of the boards are made by copying and
then changing, by resultof(). The initial position is reversed if we
are going first (because what is stored is not x's and o's, but MINE
and HIS) and can be reversed again by a reverse switch.
*/
BOARD *firstboard()
{
BOARD *temp,
*newboard();
int i,
j;
temp=newboard();
/* zero out the array */
for(i=0;i<8;i++)
for(j=0;j<8;j++)
temp->array[i][j]=EMPTY;
/* put the start position into the cells */
/* either gofirst or reversed can switch the initialization */
temp->array[3][3]=temp->array[4][4]=(!(gofirst^reversed))?MINE:HIS;
temp->array[3][4]=temp->array[4][3]=(!(gofirst^reversed))?HIS:MINE;
#if DEBUG_LEVEL > 1
temp->val=0;
#endif
return(temp);
}
/*
Getmove gets a move from the user. If the computer is an IBM-PC,
the result is immediately displayed; otherwise, the user must wait
for the computer to move. This is because of timing considerations;
dumping a board into the video ram takes very little time, while
cramming it through a 1200 baud line takes a fair while.
There is complete checking for invalid input, but no verify or revise
option (typo's will hurt you if they are legal moves).
*/
getmove(board)
BOARD **board;
{
int i,
j,
k,
n;
char ans;
BOARD *resultof(),
*temp;
long time_tick();
max=FALSE;
/* if(<the number of sons found>==0) */
if(!(n=allsons(*board)))
{
printf("You cannot move. My turn. Press return to continue: ");
#ifdef DeSmet
scanf("\n");
#else
while(getchar()!='\n');
#endif
time=time_tick();
return;
}
movenumber++;
if(n==1)
{
printf("You have only one move. Press return to continue: ");
#ifdef DeSmet
scanf("\n");
#else
while(getchar()!='\n');
#endif
time=time_tick();
temp=(*board)->sons[0];
#if DEBUG_LEVEL > 1
temp->val=worth(temp);
#endif
#ifdef IBMPC
line_number=BOARD_TOP;
putboard(temp,(*board));
#endif
reclaim(*board);
(*board)=temp;
return;
}
/* get and validity check move */
retry:
printf("Please input next move: ");
scanf("%c%d",&ans,&i);
time=time_tick();
#ifndef DeSmet
while(getchar()!='\n');
#endif
i--;
j=tolower(ans)-'a';
/* if(<position is out of range> || board[i][j]!=EMPTY) */
if(i<0 || i>7 || j<0 || j>7 || (*board)->array[i][j])
{
printf("try again, numbnut.\n");
goto retry;
}
/* scan the sons list, looking for a son with this cell occupied */
for(k=0;!((*board)->sons[k]->array[i][j]);)
/* if we have reached the end of the list without finding one */
if(++k==n)
{
printf("try again, numbnut.\n");
goto retry;
}
/* clean up stray sons */
for(i=0;i<n;i++)
if(i!=k)
reclaim((*board)->sons[i]);
temp=(*board)->sons[k];
#if DEBUG_LEVEL > 1
temp->val=worth(temp);
#endif
#ifdef IBMPC
line_number=BOARD_TOP;
putboard(temp,(*board));
#endif
reclaim(*board);
*board=temp;
}
/*
Score simply adds up the number of cells owned by each player,
reports the results, and leaves.
*/
score(board)
BOARD *board;
{
char i,
sums[3]={0,0,0},
*ptr;
ptr=board->array[0];
for(i=0;i<64;i++)
sums[*ptr++]++;
printf("I got %d; You got %d; %s.\n",sums[MINE],sums[HIS],
sums[MINE]>sums[HIS]?"I win":
(sums[MINE]==sums[HIS]?"The game is a draw":"You win"));
}
#ifdef IBMPC
/*
Fast output routines for the IBM-PC. These use _lmove(), an intersegment
block move subroutine that comes with the DeSmet C compiler.
Fast_puts(s) moves the string which is its operand, delimited by
return, newline, NULL, or any other non-printing character, and puts
it into the video ram.
*/
fast_puts(s)
char *s;
{
int i;
/*
since the character bytes in the video ram are interleaved with
attribute bytes:
for(i=0;s[i]>'\n';i++)
scr_line[2*i]=s[i];
*/
for(i=0;(scr_line[i<<1]=s[i])>'\n';i++);
/* and pad with blanks */
while(i<80)
scr_line[i++<<1]=' ';
/* now shove the line out, pronto */
_lmove(160,scr_line,_showds(),line_number,screen_seg);
/* increment the video ram line offset */
line_number+=160;
}
/* clear the top part of the screen, and home the cursor */
clear_home()
{
int i;
/* make a blank video line */
for(i=0;i<80;scr_line[i++<<1]=' ');
/* and run it out */
for(line_number=0;line_number<BOARD_TOP;line_number+=160)
_lmove(160,scr_line,_showds(),line_number,screen_seg);
#asm
mov ah,15 ; function number to read video page
int 10h ; rom bios call to make a video function request
mov dx,0 ; dh=dl=0 for top right-hand corner
mov ah,2 ; function number for cursor positioning
int 10h ; rom bios call to make a video function request
#
}
/*
Grab a representative line out of the video ram, to get the current
attribute bytes.
*/
init_screen()
{
int monochrome;
#asm
push ds ; save data segment
mov ax,40h ; load hex 40
mov ds,ax ; into data segment
mov di,[10h] ; get equipment flags
pop ds ; restore data segment
and di,30h ; check for monochrome board
mov [bp-2],di ; put value in local monochrome
#
screen_seg=(monochrome==0x30)?0xb000:0xb800;
_lmove(160,0,screen_seg,scr_line,_showds());
}
#else
/*
by careful design, fast_puts() is functionally equivalent to printf
*/
#define fast_puts printf
#endif
/*
Putboard can be used for one or two boards. For one board, make the
second parameter NULL. It calls a function fast_puts to put the string
out. This is to allow quick board drawing on a PC.
Optionally, error checking code can be compiled in, to check for the
(common) C bug of wandering of into randomn memory locations.
Throughout the function, realize the "if(new)" is functionally identical
to "if(new!=NULL)".
*/
putboard(old,new)
BOARD *old,
*new;
{
int i,
j,
k;
char s[80];
#ifdef IBMPC
line_number=BOARD_TOP; /* offset of top of board */
#endif
if(new)
fast_puts(" a b c d e f g h a b c d e f g h\n");
else
fast_puts(" a b c d e f g h\n");
/* for every row */
for(i=0;i<8;i++)
{
k=0;
if(new)
fast_puts(" --------------------------------- ---------------------------------\n");
else
fast_puts(" ---------------------------------\n");
sprintf(s," %d | ",i+1);
while(s[++k]);
/* for every column in the first board */
for(j=0;j<8;j++)
{
#if DEBUG_LEVEL > 0
if(old->array[i][j]<0 || old->array[i][j]>2)
{
printf("putboard: garbage in cell %d %d--exiting...\n",i,j);
exit(0);
}
#endif
s[k++]=print_chars[old->array[i][j]];
s[k++]=' ';
s[k++]='|';
s[k++]=' ';
}
if(new)
{
sprintf(s+k," %d | ",i+1);
while(s[++k]);
/* for every column in the second board */
for(j=0;j<8;j++)
{
#if DEBUG_LEVEL > 0
if(new->array[i][j]<0 || old->array[i][j]>2)
{
printf("putboard: garbage in cell %d %d--exiting...\n",i,j);
exit(0);
}
#endif
s[k++]=print_chars[new->array[i][j]];
s[k++]=' ';
s[k++]='|';
s[k++]=' ';
}
}
s[k++]='\n';
s[k]='\0';
fast_puts(s);
}
if(new)
fast_puts(" --------------------------------- ---------------------------------\n");
else
fast_puts(" ---------------------------------\n");
#if DEBUG_LEVEL > 1
if(new)
sprintf(s," %d %d\n",
old->val,new->val);
else
sprintf(s," %d\n",old->val);
fast_puts(s);
#endif
}
/*
Worth_1 is the worth function containing all the subtle strategic
heuristic information in the game. It is used until we can start
looking all the way to the end of the game, at which point the
optimum heuristic function, h* becomes available. That is called
worth_2, and is much simpler.
*/
worth_1(board)
BOARD *board;
{
int valsum[3]; /* buckets in which to accumulate the sums */
char *ptr, /* temporary pointers for walking through the array */
*t;
static char
val1[3]={30,0,0}, /* value of (1,1) if (0,0)=0,1,2 */
val2[3]={ 4,0,0}, /* value of (1,2) if (0,2)=0,1,2 */
val3[3]={ 5,0,0}; /* value of (1,3) if (0,3)=0,1,2 */
#define ev0 50 /* value of pos 00 (corner) */
#define ev1 4 /* value of pos 01 */
#define ev2 16 /* value of pos 02 */
#define ev3 12 /* value of pos 03 */
#define sv 20 /* value of a split on the edge */
/*
50, 4, 16, 12, 12, 16, 4, 50,
4,-30, -4, -5, -5, -4,-30, 4,
16, -4, 1, 0, 0, 1, -4, 16, This is what the board cell
12, -5, 0, 0, 0, 0, -5, 12, worth function looks like,
12, -5, 0, 0, 0, 0, -5, 12, discounting all dynamic
16, -4, 1, 0, 0, 1, -4, 16, dependancies.
4,-30, -4, -5, -5, -4,-30, 4,
50, 4, 16, 12, 12, 16, 4, 50
*/
/*
f[] is a finite state automaton used for edge strategy
It recognizes two related phenomena, which we call "splits";
positions where two pieces owned by one player on an edge are
a) separated by exactly 1 blank, or
b) separated by 1 or more of the opponent's
Invented by John White, it is structured as follows:
f[105] can be viewed as 5 tiers of 7 states, each of which requires
3 cells for the three possible input values.
Starting at one corner, you scan along an edge.
Keep always in mind that the values of board cells are
0 EMPTY
1 MINE
2 HIS
The states are numbered as follows:
state decription board
----- ---------------------------------- ------
0 currently scanning through blanks ... 0
1 just seen a square of mine ... 1
2 just seen a square if his ... 2
3 a blank following a square of mine ... 10
4 a blank following a square of his ... 20
5 one of his following one of mine ... 12
6 one of mine following one of his ... 21
The following table identifies the transitions between states.
The numbers down the left, labelling the rows, are the input
cell values. The numbers across the top are state numbers.
The contents of a cell in this matrix is the number of the
state the will be result from the input at the left from the
state above.
0 1 2 3 4 5 6
---------------------------------------------------------
0 | 0 | 3 | 4 | 0 | 0 | 4 | 3 |
---------------------------------------------------------
1 | 1 | 1 | 6 | 1 - | 1 | 6 - | 6 |
---------------------------------------------------------
2 | 2 | 5 | 2 | 2 | 2 + | 5 | 5 + |
---------------------------------------------------------
The cells containing postfix "+" or "-" symbols represent
situations in which we have found one of the key patterns,
at which point we go to the state indicated in the immediately
higher or lower tier, respectively. The final value is
simply the number of the tier we are on at the end, multiplied
by sv, the split value. Note that each state takes three array elements,
so the actual contents of the array are three times the state
numbers shown above, repeated 5 times, offset by the tier start
subscripts, with special offsets applied to the cells which
jump tiers. The array v[] is used for the last lookup, since
we are uninterested in the exact state, but just the tier, and
since the tier number must be multiplied by sv, we put the resulting
products in v[]. Finally, the tiers are organized as follows:
offset meaning value
------ ---------------- -----
0 neutral 0
21 good sv
42 bad -sv
63 very good 2*sv
84 very bad -2*sv
With this organization, if the state of the machine at time t0 is
S0 and the input is I0, the state at time t1 is f[S0+I0], and therefore
the state at time t2 is f[f[S0+I0]+I1] and so forth.
So, without further ado:
*/
static char f[105]=
{
/*----------------------------------------------------------------------------
|state 0 1 2 3 4 5 6 |
|input 0 1 2 0 1 2 0 1 2 0 1 2 0 1 2 0 1 2 0 1 2|
----------------------------------------------------------------------------*/
0, 3, 6, 9, 3,15, 12, 18, 6, 0,45, 6, 0, 3,27, 12, 60,15, 9, 18,36,
21,24,27, 30,24,36, 33, 39,27, 21, 3,27, 21,24,69, 33, 18,36, 30, 39,78,
42,45,48, 51,45,57, 54, 60,48, 42,87,48, 42,45, 6, 54,102,57, 51, 60,15,
63,66,69, 72,66,78, 75, 81,69, 63,24,69, 63,66,69, 75, 39,78, 72, 81,78,
84,87,90, 93,87,99, 96,102,90, 84,87,90, 84,87,48, 96,102,99, 93,102,57
};
/* v is the final pass of f, and is board value instead of next state */
static int v[105]=
{
0,0,0,0,0,0,0,0,0,0,-sv,0,0,0,sv,0,-sv,0,0,0,sv,
sv,sv,sv,sv,sv,sv,sv,sv,sv,sv,0,sv,sv,sv,2*sv,sv,0,sv,sv,sv,2*sv,
-sv,-sv,-sv,-sv,-sv,-sv,-sv,-sv,-sv,-sv,-2*sv,-sv,-sv,-sv,0,-sv,
-2*sv,-sv,-sv,-sv,0,
2*sv,2*sv,2*sv,2*sv,2*sv,2*sv,2*sv,2*sv,2*sv,2*sv,sv,2*sv,2*sv,
2*sv,2*sv,2*sv,sv,2*sv,2*sv,2*sv,2*sv,
-2*sv,-2*sv,-2*sv,-2*sv,-2*sv,-2*sv,-2*sv,-2*sv,-2*sv,-2*sv,-2*sv,
-2*sv,-2*sv,-2*sv,-sv,-2*sv,-2*sv,-2*sv,-2*sv,-2*sv,-sv
};
#if DEBUG_LEVEL > 1
worth_called++;
#endif
*valsum=valsum[2]=0; /* clean out buckets */
ptr=t=board->array[0]; /* set up pointers */
/* and let the finite state automoton roll--one edge in each term */
valsum[1]=
v[f[f[f[f[f[f[f[ *t ]+t[ 1]]+t[ 2]]+t[ 3]]+t[ 4]]+t[ 5]]+t[ 6]]+t[ 7]]
+v[f[f[f[f[f[f[f[ *t ]+t[ 8]]+t[16]]+t[24]]+t[32]]+t[40]]+t[48]]+t[56]]
+v[f[f[f[f[f[f[f[t[ 7]]+t[15]]+t[23]]+t[31]]+t[39]]+t[47]]+t[55]]+t[63]]
+v[f[f[f[f[f[f[f[t[56]]+t[57]]+t[58]]+t[59]]+t[60]]+t[61]]+t[62]]+t[63]];
/*
if the worth array shown in the comment above actually existed, the
next 60 or so lines might have been written
for(i=0;i<64;i++)
valsum[board[i]]+=worth[i];
but it doesn't, except in the defined constants ev0 through ev3.
Besides, it's quicker to execute 60 statements than to go through
a loop 60 times (no loop control and branching) and this function
is at the bottom of the recursion, and needs to be fast.
*/
valsum[*ptr++]+=ev0;
valsum[*ptr++]+=ev1;
valsum[*ptr++]+=ev2;
valsum[*ptr++]+=ev3;
valsum[*ptr++]+=ev3;
valsum[*ptr++]+=ev2;
valsum[*ptr++]+=ev1;
valsum[*ptr++]+=ev0;
valsum[*ptr++]+=ev1;
valsum[*ptr++]-=val1[*t];
valsum[*ptr++]-=val2[t[2]];
valsum[*ptr++]-=val3[t[3]];
valsum[*ptr++]-=val3[t[4]];
valsum[*ptr++]-=val2[t[5]];
valsum[*ptr++]-=val1[t[7]];
valsum[*ptr++]+=ev1;
valsum[*ptr++]+=ev2;
valsum[*ptr++]-=val2[t[16]];
valsum[*ptr]++;
ptr+=3;
valsum[*ptr++]++;
valsum[*ptr++]-=val2[t[23]];
valsum[*ptr++]+=ev2;
valsum[*ptr++]+=ev3;
valsum[*ptr]-=val3[t[24]];
ptr+=5;
valsum[*ptr++]-=val3[t[31]];
valsum[*ptr++]+=ev3;
valsum[*ptr++]+=ev3;
valsum[*ptr]-=val3[t[32]];
ptr+=5;
valsum[*ptr++]-=val3[t[39]];
valsum[*ptr++]+=ev3;
valsum[*ptr++]+=ev2;
valsum[*ptr++]-=val2[t[40]];
valsum[*ptr]++;
ptr+=3;
valsum[*ptr++]++;
valsum[*ptr++]-=val2[t[47]];
valsum[*ptr++]+=ev2;
valsum[*ptr++]+=ev1;
valsum[*ptr++]-=val1[t[56]];
valsum[*ptr++]-=val2[t[58]];
valsum[*ptr++]-=val3[t[59]];
valsum[*ptr++]-=val3[t[60]];
valsum[*ptr++]-=val2[t[61]];
valsum[*ptr++]-=val1[t[63]];
valsum[*ptr++]+=ev1;
valsum[*ptr++]+=ev0;
valsum[*ptr++]+=ev1;
valsum[*ptr++]+=ev2;
valsum[*ptr++]+=ev3;
valsum[*ptr++]+=ev3;
valsum[*ptr++]+=ev2;
valsum[*ptr++]+=ev1;
valsum[*ptr]+=ev0;
return(valsum[1]-valsum[2]);
}
/*
If worth_2 is being called, we are looking all the way to the end of
the game, and we can use the final board as an evaluator: the worth
of the board is the number of squares more I have than he. This number
is shifted left 2 to attempt to make the range of values returned
approximately comparable to worth_1, so that worth_2 can be used
by alphabeta when we find an early game ending.
*/
worth_2(board)
BOARD *board;
{
int valsum[3]={0,0,0};
char *ptr;
#if DEBUG_LEVEL > 1
worth_called++;
#endif
ptr=board->array[0];
/*
The following 64 lines could have been replaced with
for(i=0;i<64;i++)
valsum[board[i]]++;
but it would have been slower.
*/
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr++]++;
valsum[*ptr]++;
/* return((<number I have> - <number he has>) * 2); */
return((valsum[1]-valsum[2])<<2);
}
#ifdef IBMPC
/*
Time_tick returns the (long) number of 100'ths of seconds since
the day began, according to the PC's real-time clock. This is
useful for getting elapsed time by subtracting successive times.
*/
long time_tick()
{
#asm
mov ah,2ch ; function number for get time-of-day
int 21h ; make DOS function call
xor ah,ah ; zero ah
mov al,ch ;
mov bl,60 ; ax=60*ch
mul bl ;
xor ch,ch ; zero ch
add cx,ax ; cx=60*ch + cl is minutes
xor ah,ah ; zero ah
mov al,dh ;
mov bl,100 ; ax=100*dh
mul bl ;
xor dh,dh ; zero dh
add dx,ax ; dx=100*dh + dl is hundredths of seconds
mov bx,dx ; stash it in bx
xor dx,dx ; freeing up dx
mov ax,cx ; ax gets minutes
mov cx,6000 ;
mul cx ; times 6000 is hundredths of seconds in ax and dx
add ax,bx ; plus hundredths of seconds
jae done ; if no carry then we are done--calling convention for
; long ints in c is to return them in dx & ax
inc dx ; if carry, fixup dx
done:
#
}
#else
/* dummy routine for other systems */
long time_tick()
{
return(0);
}
#endif