These videos might hang your browser, sorry!
Tanks
Make your own AI and compete with people.  LoseThos
has a just-in-time compiler which can load-in and
compile your AI code really easily:

Unit *FindNearestUnit(I8 row,I8 col,I8 player,BoolI1 in_LOS,double range=-1)
{
 I8 i;
 double d,best_d=MAX_double,x1,y1,x2,y2;
 Unit *best=NULL;
//$FG,2$$LK,"Sqrt","MN:Sqrt"$() is slow, so work with squared distances.$FG$
 if (range<0)
     range=MAX_double;
 else
     range*=range;
 RowColToXY(x1,y1,row,col);
 for (i=0;i<MAX_UNITS;i++)
     if (units[player][i].life>0) {
         if (!in_LOS || LOS(row,col,units[player][i].row,units[player][i].col)) {
             RowColToXY(x2,y2,units[player][i].row,units[player][i].col);
             d=Sqr(x2-x1)+Sqr(y2-y1);
             if (d<=range && d<best_d) {
                 best=&units[player][i];
                 best_d=d;
             }
         }
     }
 return best;
}

void TanksMove()
{
 Unit *target,*tempu;
 I8 i;
 double x,y;
 for (i=0;i<MAX_UNITS;i++) {
     CheckUser;
     tempu=&units[cur_player][i];
     if (tempu->life>0) {
         if (target=FindNearestUnit(tempu->row,tempu->col,enemy_player,FALSE)) {
             RowColToXY(x,y,target->row,target->col);
             if (!MoveUnit(tempu,x,y)) {
                 RowColToXY(x,y,tempu->row,tempu->col);
                 MoveUnit(tempu,x+RandI2,y+RandI2);
             }
         }
     }
 }
 throw (EXCEPT_LOCAL,T_TURN_OVER);
}

void TanksFire()
{
 Unit *target,*tempu;
 I8 i;
 for (i=0;i<MAX_UNITS;i++) {
     CheckUser;
     tempu=&units[cur_player][i];
     if (tempu->life>0) {
         if (target=FindNearestUnit(tempu->row,tempu->col,enemy_player,TRUE,tempu->range*2*hex_radius))
{
             FireShot(tempu,target);
             Sleep(250*ANIMATION_DELAY);
         }
     }
 }
 throw (EXCEPT_LOCAL,T_TURN_OVER);
}


Here's the human player:
void TanksMove()
{
 Unit *tempu=NULL,*target;
 I8 cmd,ch=0,sc,p1,p2;
 view_player=cur_player;
 do {
     if (!alive_cnt[0] || !alive_cnt[1])
         throw (EXCEPT_LOCAL,T_GAME_OVER);
     cmd=GetMsg(&p1,&p2,1<<MSG_KEY_DOWN|
             1<<MSG_IP_L_DOWN|1<<MSG_IP_L_UP|
             1<<MSG_IP_R_UP);
     switch (cmd) {
         case MSG_KEY_DOWN:
             ch=p1; sc=p2;
             if (ch==CH_CR)
                 throw (EXCEPT_LOCAL,T_NEW_GAME);
             else if (ch==CH_SPACE)
                 throw (EXCEPT_LOCAL,T_TURN_OVER);
             else if (ch=='1')
                 view_player=0;
             else if (ch=='2')
                 view_player=1;
             break;
         case MSG_IP_L_DOWN:
             ch=0;
             if (CursorInWindow(Fs,p1,p2)) {
                 UpdateCursor(Fs,p1,p2);
                 if (tempu=FindUnit(cursor_row,cursor_col)) {
                     if (tempu->player==enemy_player || !tempu->remaining_movement)
                         tempu=NULL;
                     else
                         moving_unit=tempu;
                 }
             }
             break;
         case MSG_IP_L_UP:
             ch=0;
             if (CursorInWindow(Fs,p1,p2)) {
                 UpdateCursor(Fs,p1,p2);
                 target=FindUnit(cursor_row,cursor_col);
                 if (!tempu)
                     Beep;
                 else {
                     if (target)
                         Beep;
                     else
                         MoveUnit(tempu,p1+Fs->horz_scroll.pos,
                                 p2+Fs->vert_scroll.pos);
                     break;
                 }
             }
             moving_unit=tempu=NULL;
             break;
         case MSG_IP_R_UP:
             ch=0;
             if (CursorInWindow(Fs,p1,p2))
                 throw (EXCEPT_LOCAL,T_TURN_OVER);
             break;
     }
 } while (ch!=CH_CTRLQ && ch!=CH_ESC);
 GetMsg(NULL,NULL,1<<MSG_KEY_UP);
 throw (EXCEPT_LOCAL,T_EXIT_GAME);
}

void TanksFire()
{
 Unit *tempu=NULL,*target;
 I8 cmd,ch=0,sc,p1,p2;
 view_player=cur_player;
 do {
     if (!alive_cnt[0] || !alive_cnt[1])
         throw (EXCEPT_LOCAL,T_GAME_OVER);
     cmd=GetMsg(&p1,&p2,1<<MSG_KEY_DOWN|
             1<<MSG_IP_L_DOWN|1<<MSG_IP_L_UP|
             1<<MSG_IP_R_UP);
     switch (cmd) {
         case MSG_KEY_DOWN:
             ch=p1; sc=p2;
             if (ch==CH_CR)
                 throw (EXCEPT_LOCAL,T_NEW_GAME);
             else if (ch==CH_SPACE)
                 throw (EXCEPT_LOCAL,T_TURN_OVER);
             else if (ch=='1')
                 view_player=0;
             else if (ch=='2')
                 view_player=1;
             break;
         case MSG_IP_L_DOWN:
             ch=0;
             if (CursorInWindow(Fs,p1,p2)) {
                 UpdateCursor(Fs,p1,p2);
                 if (tempu=FindUnit(cursor_row,cursor_col)) {
                     if (tempu->player==enemy_player || tempu->fired)
                         tempu=NULL;
                     else {
                         RVSetUp(cur_player);
                         RowColToXY(fire_radius_x,fire_radius_y,tempu->row,tempu->col);
                         fire_radius=tempu->range*2*hex_radius;
                         RecalcVisible(RV_ONE_FRIENDLY_UNIT,tempu);
                     }
                 }
             }
             break;
         case MSG_IP_L_UP:
             ch=0;
             if (CursorInWindow(Fs,p1,p2)) {
                 UpdateCursor(Fs,p1,p2);
                 target=FindUnit(cursor_row,cursor_col);
                 if (!tempu)
                     Beep;
                 else {
                     if (!target || target->player!=enemy_player || !Bt(&target->visible,0))
                         Beep;
                     else
                         FireShot(tempu,target);
                     RecalcVisible(RV_UPDATE_FRIENDLY_UNIT,tempu);
                 }
             }
             tempu=NULL;
             fire_radius=0;
             break;
         case MSG_IP_R_UP:
             ch=0;
             if (CursorInWindow(Fs,p1,p2))
                 throw (EXCEPT_LOCAL,T_TURN_OVER);
             break;
     }
 } while (ch!=CH_CTRLQ && ch!=CH_ESC);
 GetMsg(NULL,NULL,1<<MSG_KEY_UP);
 throw (EXCEPT_LOCAL,T_EXIT_GAME);
}


Here's the program:
/*$WW,1$$FG,2$
Alteration Ideas:
 * Add another fire phase
 * Add opportunity fire
 * Add overruns
 * Add close assaults
 * Add more unit types
 * Add artillery
 * Add aircraft
 * Add troop transport
 * Add land mines
 * Add buildings
 * Adjust terrain cost
 * Adjust terrain defense bonuses
 * Add bridges
 * Add amphibious scenareos
 * Add vehicle wreckage
 * Add scoring
 * Add game turn limit
$FG$$WW,0$*/

#define MAP_WIDTH                603
#define MAP_HEIGHT                452
#define MAX_UNITS                32
#define ODDS                        1.0
#define FRIENDLY_ARMOR_PERCENT        50
#define ENEMY_ARMOR_PERCENT        50

/*$WW,1$$FG,2$Constants are often faster when compiled.  This is an advantage of just-in-time
compilation.  $FG,2$You cannot change them when you start a game over, however.  $FG,2$A down-side of
this is you cannot use the $FG,4$CTRL-C$FG,2$ auto-indent feature.
$FG$$WW,0$*/

#exe {
 TssStruct *tss;
 PopUpFile("/LT/Apps/Tanks/RotateTank.CPZ",NULL,&tss);
 double d;
 if (!PopUpNoYes("Tournament Settings")) {
     d=PopUpRangeDoubleLog(300,2000,20,"%4f","Map Width In Pixels\r\n");
     ExePrintF("#define MAP_WIDTH %f\r\n",d);
     ExePrintF("#define MAP_HEIGHT %f\r\n",480.0/640.0*d);
     d=PopUpRangeDoubleExp(8,512,2,"%3f","Enemy Units\r\n");
     ExePrintF("#define MAX_UNITS %f\r\n",d);
     d=PopUpRangeDouble(0.2,1.0,0.05,"%4.2f to 1.00","Odds\r\n");
     ExePrintF("#define ODDS %4.2f\r\n",d);
     d=PopUpRangeDouble(0,100,10,"%3f% %%","Friendly Armor Percent\r\n");
     ExePrintF("#define FRIENDLY_ARMOR_PERCENT %f\r\n",d);
     d=PopUpRangeDouble(0,100,10,"%3f% %%","Enemy Armor Percent\r\n");
     ExePrintF("#define ENEMY_ARMOR_PERCENT %f\r\n",d);
 }
 d=PopUpRangeDouble(0,100,25,"%3f% %%","Animation Delay\r\n");
 ExePrintF("#define ANIMATION_DELAY %5.3f\r\n",d/100.0);
 XTalk(tss," ");
 WaitTaskIdle(tss);
 Kill(tss);
};

#define HEX_SIDE        11
double dc=HEX_SIDE*Cos(60.0/180*pi),
           ds=HEX_SIDE*Sin(60.0/180*pi),
           hex_radius=ds+0.01; //slop

I8 map_cols=(MAP_WIDTH-dc)/(2*HEX_SIDE+2*dc),
   map_rows=ToI8((MAP_HEIGHT-ds)/ds)&~1,
   map_width=map_cols*(2*HEX_SIDE+2*dc)+dc,
   map_height=map_rows*ds+ds+1;

GrBitMap *map;
I1 terrain[map_rows][map_cols];


//$FG,2$Centers of hexes$FG$
class Point
{
 double x,y;
};
Point hex_centers[map_rows][map_cols];

I8 show_visible_row,show_visible_col;
BoolI1 roads[map_rows][map_cols],
           rivers[map_rows][map_cols],
           visible_map[map_rows][map_cols];

//$FG,2$Other options for PLAINS are WHITE or YELLOW$FG$
#define PLAINS                LTGREEN
#define TREES                GREEN
#define MOUNTAINS        DKGRAY

I1 movement_costs[16];
movement_costs[PLAINS]=2;
movement_costs[TREES]=6;
movement_costs[MOUNTAINS]=10;

//$FG,2$These are used to display a range circle when they player$FG$
//$FG,2$is firing.$FG$
double fire_radius,fire_radius_x,fire_radius_y;

//$FG,2$These display "phase", "turn" and "game over".$FG$
I1 msg_buf[80];
U8 msg_off_timeout; //$FG,2$Goes away after a time.$FG$


/*$WW,1$$FG,2$I got tricky by not defining a color right away $FG,2$in these $LK,"GrElem","MN:GrElem"$s
so they can work for both players by setting $FG,4$base->color$FG$ $FG,2$before drawing them.  I
actually made these graphics by defining a color $FG,2$in the $FG,4$CTRL-R$FG,2$ menu, drawing the unit
and deleting the color.

I had to leave a gap between the tank tread and body because of how it is rendered when rotated.$FG$$FG$
$WW,0$*/

$PI,"<1>",1$


$PI,"<2>",2$


//$FG,2$This is an infantry.$FG$

$PI,"<3>",3$


class Unit
{
 U1 *img;
 I8 num,row,col,
       armored_attack,unarmored_attack,armor;
 I1 type,player,facing,movement,life,
       range,remaining_movement,accuracy;
 BoolI1 visible[2],fired,infantry;
};

Unit units[2][MAX_UNITS];

//$FG,2$ Bt(visible_unit_bitmap,p1+p0*MAX_UNITS)$FG$
U1 visible_unit_bitmap[2][MAX_UNITS*MAX_UNITS>>3];

#define PHASE_START        0
#define PHASE_MOVE        0
#define PHASE_MOVE0        0
#define PHASE_MOVE1        1
#define PHASE_FIRE        2
#define PHASE_FIRE0        2
#define PHASE_FIRE1        3
#define PHASE_END        4

I8 phase,cur_player,enemy_player,view_player,turn,cursor_row,cursor_col,alive_cnt[2],
   move_routines[2],fire_routines[2];


void Toward(I8 &row,I8 &col,I8 direction)
{

//$FG,2$We want this "atomic" in a multitasking sense.$FG$
 BoolI1 old_preempt=Preempt(OFF);

 switch (direction) {
     case 0:
         row-=2;
         break;
     case 1:
         if (row&1)
             col++;
         row--;
         break;
     case 2:
         if (row&1)
             col++;
         row++;
         break;
     case 3:
         row+=2;
         break;
     case 4:
         if (!(row&1))
             col--;
         row++;
         break;
     case 5:
         if (!(row&1))
             col--;
         row--;
         break;
 }
 Preempt(old_preempt);
}

I8 FacingChange(I8 f1,I8 f2)
{
 I8 result=(f1+6-f2)%6;
 if (result>=3)
     return 6-result;
 else
     return result;
}

void RowColToXY(double &x,double &y,I8 row,I8 col)
{
 Point *c;
 row=LimitI8(row,0,map_rows);
 col=LimitI8(col,0,map_cols);
 c=&hex_centers[row][col];
 x=c->x;
 y=c->y;
}

void XYToRowCol(I8 &row,I8 &col,double x,double y)
{
 col=(x-dc/2)/(HEX_SIDE+dc);
 if (col&1)
     row=ToI8((y-ds)/(2*ds))*2+1;
 else
     row=ToI8(y/(2*ds))*2;
 col>>=1;
 row=LimitI8(row,0,map_rows-1);
 col=LimitI8(col,0,map_cols-1);
}

Unit *FindUnit(I8 row,I8 col)
{//$FG,2$Finds unit in a hexagon.$FG$
 I8 i,j;
 for (j=0;j<2;j++)
     for (i=0;i<MAX_UNITS;i++)
         if (units[j][i].life>0 &&
                 units[j][i].row==row &&
                 units[j][i].col==col)
             return &units[j][i];
 return NULL;
}

BoolI1 CursorInWindow(TssStruct *tss,I8 x,I8 y)
{
 if (0<=x<tss->win_pixel_width &&
         0<=y<tss->win_pixel_height)
     return TRUE;
 else
     return FALSE;
}

void UpdateCursor(TssStruct *tss,I8 x,I8 y)
{

//$FG,2$We want this "atomic" in a multitasking sense.$FG$
 BoolI1 old_preempt=Preempt(OFF);

 if (CursorInWindow(tss,x,y))
     XYToRowCol(cursor_row,cursor_col,x+tss->horz_scroll.pos,y+tss->vert_scroll.pos);
 Preempt(old_preempt);
}

$BG,14$class LOSCtrl
{
 I8 r1,c1,r2,c2,distance;
 BoolI1 result;
};

void LOSPlot(LOSCtrl *l,I8 x,I8 y,I8 z)
{ //$FG,2$We got tricky and used $FG,4$z$FG,2$ as the distance from the start of the line.$FG$
 I8 row,col;
 XYToRowCol(row,col,x,y);
 if ((row!=l->r1 || col!=l->c1) &&
         (row!=l->r2 || col!=l->c2) &&
         terrain[row][col]!=PLAINS) {
     if (terrain[l->r1][l->c1]==MOUNTAINS) {
         if (terrain[row][col]==MOUNTAINS || z>l->distance>>1)
             l->result=FALSE;
     } else if (terrain[l->r2][l->c2]==MOUNTAINS) {
         if (terrain[row][col]==MOUNTAINS || z<=l->distance>>1)
             l->result=FALSE;
     } else
         l->result=FALSE;
 }
}

BoolI8 LOS(I8 r1,I8 c1,I8 r2,I8 c2)
{
 double x1,y1,x2,y2;
 LOSCtrl l;
 RowColToXY(x1,y1,r1,c1);
 RowColToXY(x2,y2,r2,c2);
 l.r1=r1; l.c1=c1;
 l.r2=r2; l.c2=c2;
 l.distance=Sqrt(SqrI8(x1-x2)+SqrI8(y1-y2));
 l.result=TRUE;
 Line(&l,x1,y1,0,x2,y2,l.distance,&LOSPlot);
 return l.result;
}

#define RV_ONE_FRIENDLY_UNIT        0
#define RV_UPDATE_FRIENDLY_UNIT        1
#define RV_FRIENDLY_UNIT_DIED        3
#define RV_ONE_ENEMY_UNIT        4
#define RV_ALL_UNITS                5

class MPCtrl1
{
 I8 mode,lo,hi;
 Unit *tempu;
};

void RVSetUp(I8 player)
{
 I8 i;
 Unit *u0,*u1;
 u0=&units[player][0];
 u1=&units[player^1][0];
 for (i=0;i<MAX_UNITS;i++,u0++,u1++) {
     LBtr(&u1->visible[player],0);
     LAssignBit(&u0->visible[player],0,u0->life>0);
 }
}

void RVMerge(I8 player)
{
 I8 i,j;
 Unit *u1;
 U1 *dst,*src,*mask=MAllocZ(MAX_UNITS>>3);
 src=&visible_unit_bitmap[player];
 for (j=0;j<MAX_UNITS;j++) { //p0
     dst=mask;
     for (i=0;i<MAX_UNITS>>3;i++) //p1
         *dst++|=*src++;
 }
 u1=&units[player^1][0];
 for (j=0;j<MAX_UNITS;j++,u1++)
     LAssignBit(&u1->visible[player],0,Bt(mask,j) && u1->life>0);
 Free(mask);
}


BoolI8 MPRecalcVisible(MPCtrl1 *job)
{
 BoolI1 result=FALSE,seen;
 I8 i,j,row,col;
 double x1,y1,x2,y2,d,range;
 Unit *u0,*u1;
 WbInvd;
 u0=&units[cur_player][job->lo];
 u1=&units[enemy_player][job->lo];
 if (job->tempu) {
     row=job->tempu->row;
     col=job->tempu->col;
     range=job->tempu->range*2*hex_radius;
     range*=range;
 }
 switch (job->mode) {
     case RV_UPDATE_FRIENDLY_UNIT:
     case RV_ONE_FRIENDLY_UNIT:
         if (job->mode==RV_UPDATE_FRIENDLY_UNIT)
             range=MAX_double;
         RowColToXY(x1,y1,row,col);
         for (i=job->lo;i<job->hi;i++,u1++) {
             seen=FALSE;
             if (u1->life>0 &&
                     LOS(row,col,u1->row,u1->col)) {
                     RowColToXY(x2,y2,u1->row,u1->col);
                 d=Sqr(x2-x1)+Sqr(y2-y1);
                 if (d<range) {
                     seen=TRUE;
                     LBts(&u1->visible[cur_player],0);
                 }
             }
             if (job->mode==RV_UPDATE_FRIENDLY_UNIT)
                 LAssignBit(&visible_unit_bitmap[cur_player],i+job->tempu->num*MAX_UNITS,seen);
         }
         break;
     case RV_ONE_ENEMY_UNIT:
         RowColToXY(x1,y1,row,col);
         for (i=job->lo;i<job->hi;i++,u1++)
             if (u1->life>0 &&
                     LOS(row,col,u1->row,u1->col)) {
                 LBts(&visible_unit_bitmap[enemy_player],job->tempu->num+i*MAX_UNITS);
                 result=TRUE;
             } else
                 LBtr(&visible_unit_bitmap[enemy_player],job->tempu->num+i*MAX_UNITS);
         break;
     case RV_ALL_UNITS:
         u0=&units[cur_player][0];
         for (i=0;i<MAX_UNITS;i++,u0++)
             if (u0->life>0) {
                 RowColToXY(x1,y1,u0->row,u0->col);
                 u1=&units[enemy_player][job->lo];
                 for (j=job->lo;j<job->hi;j++,u1++) {
                     if (u1->life>0 &&
                         LOS(u0->row,u0->col,u1->row,u1->col)) {
                         LBts(&u1->visible[cur_player],0);
                         LBts(&visible_unit_bitmap[cur_player],j+i*MAX_UNITS);
                     } else
                         LBtr(&visible_unit_bitmap[cur_player],j+i*MAX_UNITS);
                 }
             } else
                 for (j=job->lo;j<job->hi;j++)
                     LBtr(&visible_unit_bitmap[cur_player],j+i*MAX_UNITS);
         u0=&units[enemy_player][0];
         for (i=0;i<MAX_UNITS;i++,u0++)
             if (u0->life>0) {
                 RowColToXY(x1,y1,u0->row,u0->col);
                 u1=&units[cur_player][job->lo];
                 for (j=job->lo;j<job->hi;j++,u1++) {
                     if (u1->life>0 &&
                         LOS(u0->row,u0->col,u1->row,u1->col)) {
                         LBts(&u1->visible[enemy_player],0);
                         LBts(&visible_unit_bitmap[enemy_player],j+i*MAX_UNITS);
                     } else
                         LBtr(&visible_unit_bitmap[enemy_player],j+i*MAX_UNITS);
                 }
             } else
                 for (j=job->lo;j<job->hi;j++)
                     LBtr(&visible_unit_bitmap[enemy_player],j+i*MAX_UNITS);
         break;
 }
 WbInvd;
 return result;
}

BoolI8 RecalcVisible(I8 mode,Unit *tempu=NULL)
{
/*$WW,1$$FG,2$I think we have issues with the boundary result unit.visible data items if two occur in
the same CPU cache line.  This is not a catastropic risk with this program.  You could increase
$FG,4$sizeof(Unit)$FG,2$ beyond one cache line.
$WW,0$$FG$*/
 I8 i,hi,k,cnt;
 BoolI8 result;

/*$FG,2$$WW,1$The compiler doesn't go out of it's way to know if something is constant.;-)  This just
compiles with the value at compile time, an advantage of just-in-time over static binaries.  LoseThos
has a limited stack size, so don't get in the habit.  $BL,14$$LK,"MAlloc","MN:MAlloc"$()$BL,15$ would
probably be the better choice.
$FG$$WW,0$*/
 MPCtrl1 job[mp_cnt];
 MPCmdStruct *cmd[mp_cnt];


 if (mode==RV_FRIENDLY_UNIT_DIED) {
     MemSet(&visible_unit_bitmap[enemy_player]+tempu->num*MAX_UNITS>>3,0,MAX_UNITS>>3);
     RVMerge(enemy_player);
     return;
 }

 cnt=mp_cnt; //$FG,2$cores$FG$
 hi=MAX_UNITS;
 if (mode==RV_ONE_ENEMY_UNIT) {
     for (hi--;hi>=0;hi--)
         if (units[enemy_player][hi].life>0)
             break;
     hi++;
 }
 k=hi;
 if (hi/mp_cnt<2)
     cnt=1;
 for (i=0;i<cnt;i++) {
     job[i].mode=mode;
     job[i].tempu=tempu;
     job[i].hi=k;
     k-=hi/cnt;
     if (k<0) k=0;
     if (i==cnt-1) k=0;
     job[i].lo=k;
 }
 WbInvd;


/*$FG,2$$WW,1$I hesitate to use all cores because I have plans, perhaps, to dedicate a core to sound
waveform generation, or some other application might be using cores.  Perhaps, I'll decrement$BL,14$
$LK,"mp_cnt","MN:mp_cnt"$$BL,15$ and place dedicated cores above that number.  I'm not going to worry
about other apps -- that doesn't fit the philosophy of LoseThos.
$WW,0$$FG$*/
 for (i=cnt-1;i>0;i--)
     cmd[i]=MPJob(&MPRecalcVisible,&job[i],0,1<<i);
 result=MPRecalcVisible(&job[0]);
 for (i=cnt-1;i>0;i--)
     if (MPJobResult(cmd[i]))
         result=TRUE;
 if (mode==RV_UPDATE_FRIENDLY_UNIT)
     RVMerge(cur_player);
 return result;
}

class MPCtrl2
{
 I8 lo,hi,row,col;
};

void MPRecalcVisibleMap(MPCtrl2 *job)
{
 I8 i,j;
 WbInvd;
 for (j=job->lo;j<job->hi;j++)
     for (i=0;i<map_cols;i++)
         if (LOS(job->row,job->col,j,i))
             visible_map[j][i]=TRUE;
         else
             visible_map[j][i]=FALSE;
 WbInvd;
}

BoolI8 RecalcVisibleMap(I8 row,I8 col)
{
 I8 i,hi,k,cnt;
 MPCtrl2 job[mp_cnt];
 MPCmdStruct *cmd[mp_cnt];

 cnt=mp_cnt; //$FG,2$cores$FG$
 hi=map_rows;
 k=hi;
 if (hi/mp_cnt<2)
     cnt=1;
 for (i=0;i<cnt;i++) {
     job[i].row=row;
     job[i].col=col;
     job[i].hi=k;
     k-=hi/cnt;
     if (k<0) k=0;
     if (i==cnt-1) k=0;
     job[i].lo=k;
 }
 WbInvd;
 for (i=cnt-1;i>0;i--)
     cmd[i]=MPJob(&MPRecalcVisibleMap,&job[i],0,1<<i);
 MPRecalcVisibleMap(&job[0]);
 for (i=cnt-1;i>0;i--)
     MPJobResult(cmd[i]);
}

$BG,2$I8 MoveCost(Unit *tempu,I8 r,I8 c,I8 facing)
{
 I8 result;
 if (tempu->infantry)
     result=0;
 else {
     result=FacingChange(facing,tempu->facing);
     if (result>0) result--;
 }
 if (roads[r][c] && roads[tempu->row][tempu->col])
     result+=1;
 else {
     if (tempu->infantry)
         result+=2;
     else {
         result+=movement_costs[terrain[r][c]];
         if (rivers[r][c])
             result=tempu->movement;
     }
 }
 return result;
}

I8 MoveOneHex(I8 &row,I8 &col,double x,double y)
{
 I8 direction,best_direction=-1,r,c;
 double d,best_d,x1,y1;
 RowColToXY(x1,y1,row,col);
 best_d=Sqr(x1-x)+Sqr(y1-y);
 for (direction=0;direction<6;direction++) {
     r=row; c=col;
     Toward(r,c,direction);
     RowColToXY(x1,y1,r,c);
     d=Sqr(x1-x)+Sqr(y1-y);
     if (0<=r<map_rows &&
             0<=c<map_cols &&
             d<best_d) {
         best_d=d;
         best_direction=direction;
     }
 }
 if (best_direction>=0) {
     Toward(row,col,best_direction);
     return best_direction;
 } else
     return -1;
}

BoolI1 moving=FALSE;
I8 move_x,move_y;
double move_facing;
Unit *moving_unit;

void MovePlot(void aux,I8 x,I8 y,I8 z)
{
 nounusedwarn z,aux;
 move_x=x; move_y=y;
 Sleep(5*ANIMATION_DELAY);
}

void MoveUnitAnimation(Unit *tempu,I8 r,I8 c,I8 facing)
{
 double x1,y1,x2,y2,f=facing*60.0*pi/180.0;
 BoolI1 old_visible=Bt(&tempu->visible[cur_player],0);
 if (!Bt(&tempu->visible[cur_player],0))
     return;
 moving_unit=tempu;
 LBtr(&tempu->visible[cur_player],0);
 RowColToXY(x1,y1,tempu->row,tempu->col);
 move_x=x1; move_y=y1;
 moving=TRUE;
 if (tempu->infantry)
     Sound(300);
 else {
     move_facing=tempu->facing*60.0*pi/180.0;
     Sound(150);
     while (Unwrap(f-move_facing,-pi)<=0) {
         move_facing-=0.03;
         Sleep(5*ANIMATION_DELAY);
     }
     while (Unwrap(f-move_facing,-pi)>0) {
         move_facing+=0.03;
         Sleep(5*ANIMATION_DELAY);
     }
     Sound(100);
 }
 move_facing=f;
 RowColToXY(x2,y2,r,c);
 Line(NULL,x1,y1,0,x2,y2,0,&MovePlot);
 Sound(0);
 moving_unit=NULL;
 LAssignBit(&tempu->visible[cur_player],0,old_visible);
 moving=FALSE;
}

BoolI8 MoveUnit(Unit *tempu,I8 x,I8 y)
{
 I8 r,c,r0=tempu->row,c0=tempu->col,i,facing;
 while (tempu->remaining_movement>0) {
     r=tempu->row;
     c=tempu->col;
     if ((facing=MoveOneHex(r,c,x,y))<0)
         break;
     else {
         i=MoveCost(tempu,r,c,facing);
         if (i>tempu->movement)
             i=tempu->movement;
         if (tempu->remaining_movement>=i && !FindUnit(r,c)) {
             MoveUnitAnimation(tempu,r,c,facing);
             tempu->facing=facing;
             tempu->remaining_movement-=i;
             tempu->row=r;
             tempu->col=c;
             RecalcVisible(RV_UPDATE_FRIENDLY_UNIT,tempu);
             LAssignBit(&tempu->visible[enemy_player],0,RecalcVisible(RV_ONE_ENEMY_UNIT,tempu));
         } else
             break;
     }
 }
 if (tempu->row!=r0 || tempu->col!=c0)
     return TRUE;
 else
     return FALSE;
}

$BG,4$BoolI1 firing=FALSE;
I8 fire_x,fire_y;

void FireSoundTask(BoolI1 hit)
{
 I8 i;
 Fs->end_task_cb=&SoundEndTaskCB;
 while (sys_freq) //see $LK,"Sound","MN:Sound"$()
     SwapInNextTask;
 if (hit)
     for (i=0;i<25;i++) {
         Sound(RandU2%50+100);
         Sleep(20*ANIMATION_DELAY);
     }
 else
     for (i=0;i<300;i++) {
         Sound(RandU2%100+750);
         Sleep(3*ANIMATION_DELAY);
     }
}

void ExplosionSoundTask()
{
 I8 i;
 Fs->end_task_cb=&SoundEndTaskCB;
 while (sys_freq) //see $LK,"Sound","MN:Sound"$()
     SwapInNextTask;
 for (i=0;i<400;i++) {
     Sound(RandU2%3000+1000);
     Sleep(2*ANIMATION_DELAY);
 }
}

void FirePlot(void aux,I8 x,I8 y,I8 z)
{
 nounusedwarn z,aux;
 fire_x=x; fire_y=y;
 firing=TRUE;
 Sleep(3*ANIMATION_DELAY);
}

void FireShot(Unit *tempu,Unit *target)
{
 I8 r,c,facing,add_modifier,
     t1=terrain[tempu->row][tempu->col],
     t2=terrain[target->row][target->col];
 double x1,y1,x2,y2,d,a,dammage=0,range_factor;
 BoolI1 hit;

 if (tempu->life<=0 || target->life<=0 ||
         tempu->range<=0 || tempu->accuracy<=0)
     return;
 RowColToXY(x1,y1,tempu->row,tempu->col);
 RowColToXY(x2,y2,target->row,target->col);

 range_factor=Sqrt(Sqr(x2-x1)+Sqr(y2-y1))/(tempu->range*ds);
 if (t2==TREES)
     add_modifier=20;
 else
     add_modifier=0;
 d=(100.0*range_factor/2.0*RandU2/MAX_U2+add_modifier)/tempu->accuracy-1.0;
 if (d<0)
     hit=TRUE;
 else
     hit=FALSE;

 Sound(0);
 Spawn(&FireSoundTask,hit);
 if (hit)
     Line(NULL,x1,y1,0,x2,y2,0,&FirePlot);
 else {
     a=pi*2*RandU2/MAX_U2;
     d=(d+0.5)*HEX_SIDE;
     Line(NULL,x1,y1,0,x2+d*Cos(a),y2+d*Sin(a),0,&FirePlot);
 }
 firing=FALSE;
 tempu->fired=TRUE;

 if (hit) {
     r=target->row; c=target->col;
     if ((facing=MoveOneHex(r,c,x1,y1))>=0)
         facing=FacingChange(facing,target->facing);
     else
         facing=0;
     dammage=200.0*RandU2/MAX_U2;
     if (target->armor) {
         d=target->armor/100.0*(5.0-facing)/5.0;
         if (d>=0)
             dammage*=(tempu->armored_attack/100.0)/d;
         else
             dammage=0;
     } else {
         d=2.0-range_factor;
         if (d>=0)
             dammage*=(tempu->unarmored_attack/100.0)*d;
         else
             dammage=0;
     }
     dammage=Round(dammage);
     if (dammage>0) {
         if (dammage>=target->life) {
             Spawn(&ExplosionSoundTask,hit);
             target->life=0;
             RecalcVisible(RV_FRIENDLY_UNIT_DIED,target);
             alive_cnt[target->player]--;
         } else {
             if (target->armor) {
                 if (dammage>0.6*target->life)
                     target->movement=0;
             } else
                 target->life-=dammage;
         }
     }
 }
 while (sys_freq) //see $LK,"Sound","MN:Sound"$()
     SwapInNextTask;
}

$BG,14$void DrawHexes()
{
 double dx=2*HEX_SIDE+2*dc,dy=2*ds,
         x,y,x1,y1,x2,y2;
 I8 i,j;
 map->color=WHITE;
 GrBox(map,0,0,map->width,map->height);
 map->color=BLACK;
 y=0;
 for (j=0;j<map_rows;j+=2) {
     x=dc;
     GrLine(map,x,y,x-dc,y+ds);
     GrLine(map,x-dc,y+ds,x,y+2*ds);
     for (i=0;i<map_cols;i++) {
         x1=x; y1=y;
         x2=x1+HEX_SIDE; y2=y1;
         GrLine(map,x1,y1,x2,y2);
         x1=x2; y1=y2;
         x2+=dc; y2+=ds;
         GrLine(map,x1,y1,x2,y2);
         GrLine(map,x2,y2,x2-dc,y2+ds);
         x1=x2; y1=y2;
         x2+=HEX_SIDE;
         GrLine(map,x1,y1,x2,y2);
         GrLine(map,x2,y2,x2+dc,y2+ds);
         x1=x2; y1=y2;
         x2+=dc; y2-=ds;
         if (j || i<map_cols-1)
             GrLine(map,x1,y1,x2,y2);
         x+=dx;
     }
     y+=dy;
 }
 x=dc;
 for (i=0;i<map_cols;i++) {
     x1=x; y1=y;
     x2=x1+HEX_SIDE; y2=y1;
     GrLine(map,x1,y1,x2,y2);
     x1=x2; y1=y2;
     x2+=dc; y2+=ds;
     GrLine(map,x1,y1,x2,y2);
     x1=x2; y1=y2;
     x2+=HEX_SIDE;
     GrLine(map,x1,y1,x2,y2);
     x1=x2; y1=y2;
     x2+=dc; y2-=ds;
     GrLine(map,x1,y1,x2,y2);
     x+=dx;
 }
}

void MakeTerrain(I1 color,I8 cnt,I8 cluster_lo,I8 cluster_hi)
{
 I8 i,j,l,row,col;
 for (i=0;i<cnt;i++) {
     col=RandU4%map_cols;
     row=RandU4%map_rows;
     l=cluster_lo+RandU2%(cluster_hi-cluster_lo+1);
     for (j=0;j<l;j++) {
         terrain[row][col]=color;
         Toward(row,col,RandU2%6);
         col=LimitI8(col,0,map_cols-1);
         row=LimitI8(row,0,map_rows-1);
     }
 }
}

void MakeRivers()
{
 I8 i,row,col,direction;
 for (i=0;i<4;i++) {
     row=RandU4%map_rows;
     col=RandU4%map_cols;
     direction=RandU2%6;
     while (TRUE) {
         rivers[row][col]=TRUE;
         Toward(row,col,direction);
         if (!(0<=row<map_rows && 0<=col<map_cols))
             break;
         if (!(RandU2%4))
             direction=(direction+(7-RandU2%3))%6;
     }
 }
}

void MakeRoads()
{
 I8 i,row,col,direction;
 for (i=0;i<5;i++) {
     row=RandU4%map_rows;
     col=RandU4%map_cols;
     direction=RandU2%6;
     while (TRUE) {
         roads[row][col]=TRUE;
         Toward(row,col,direction);
         if (!(0<=row<map_rows && 0<=col<map_cols))
             break;
         if (!(RandU2%3))
             direction=(direction+(7-RandU2%3))%6;
     }
 }
}

void DrawTerrain()
{
 I8 i,j;
 double x,y;
 for (j=0;j<map_rows;j++)
     for (i=0;i<map_cols;i++) {
         map->color=terrain[j][i];
         RowColToXY(x,y,j,i);
         GrFloodFill(map,x,y);
     }
}

void DrawRivers()
{
 I8 i,j,k,r,c;
 double x1,y1,x2,y2;
 for (j=0;j<map_rows;j++)
     for (i=0;i<map_cols;i++) {
         if (rivers[j][i]) {
             RowColToXY(x1,y1,j,i);
             for (k=0;k<6;k++) {
                 r=j;c=i;
                 Toward(r,c,k);
                 if (0<=r<map_rows && 0<=c<map_cols &&
                     rivers[r][c]) {
                     RowColToXY(x2,y2,r,c);
                     map->color=LTBLUE;
                     map->pen_width=4;
                     GrLine3(map,x1,y1,0,x2,y2,0);
                     map->color=BLUE;
                     map->pen_width=2;
                     GrLine3(map,x1,y1,0,x2,y2,0);
                 }
             }
         }
     }
}

void DrawRoads()
{
 I8 i,j,k,r,c;
 double x1,y1,x2,y2;
 map->color=RED;
 map->pen_width=3;
 for (j=0;j<map_rows;j++)
     for (i=0;i<map_cols;i++) {
         if (roads[j][i]) {
             RowColToXY(x1,y1,j,i);
             for (k=0;k<6;k++) {
                 r=j;c=i;
                 Toward(r,c,k);
                 if (0<=r<map_rows && 0<=c<map_cols &&
                     roads[r][c]) {
                     RowColToXY(x2,y2,r,c);
                     GrLine3(map,x1,y1,0,x2,y2,0);
                 }
             }
         }
     }
}


void CalcHexCenters;
{
 I8 i,j;
 double x,y;
 for (j=0;j<map_rows;j++)
     for (i=0;i<map_cols;i++) {
         x=(2*HEX_SIDE+2*dc)*i+HEX_SIDE/2+dc;
         if (j&1)
             x+=HEX_SIDE+dc;
         y=ds*(j+1);
         hex_centers[j][i].x=x;
         hex_centers[j][i].y=y;
     }
}

void DrawDots()
{
 I8 i,j;
 double x,y;
 map->color=BLACK;
 for (j=0;j<map_rows;j++)
     for (i=0;i<map_cols;i++) {
         RowColToXY(x,y,j,i);
         GrPlot(map,x,y);
     }
}

void InitMap()
{
 CalcHexCenters;
 DrawHexes;
 MemSet(terrain,PLAINS,sizeof(terrain));
 MemSet(roads,FALSE,sizeof(roads));
 MemSet(rivers,FALSE,sizeof(rivers));
 MemSet(visible_map,FALSE,sizeof(visible_map));
 MakeTerrain(MOUNTAINS,0.03*map_cols*map_cols,5,35);
 MakeTerrain(TREES,0.03*map_cols*map_cols,5,35);
 DrawTerrain;
 MakeRivers;
 DrawRivers;
 MakeRoads;
 DrawRoads;
 DrawDots;
}

void InitUnits()
{
 I8 i,j,row,col,type;
 MemSet(units,0,sizeof(units));
 alive_cnt[0]=Round(MAX_UNITS*ODDS);
 alive_cnt[1]=MAX_UNITS;
 for (j=0;j<2;j++)
     for (i=0;i<alive_cnt[j];i++) {
         units[j][i].player=j;
         units[j][i].num=i;
         units[j][i].life=100;
         units[j][i].facing=RandU2%6;
         if (!j) {
             if (i>=Round(MAX_UNITS*ODDS*FRIENDLY_ARMOR_PERCENT/100.0))
                 type=2;
             else if (i>=Round(0.5*MAX_UNITS*ODDS*FRIENDLY_ARMOR_PERCENT/100.0))
                 type=1;
             else
                 type=0;
         } else {
             if (i>=Round(MAX_UNITS*ENEMY_ARMOR_PERCENT/100.0))
                 type=2;
             else if (i>=Round(0.5*MAX_UNITS*ENEMY_ARMOR_PERCENT/100.0))
                 type=1;
             else
                 type=0;
         }
         units[j][i].type=type;
         switch (type) {
             case 0: //Light Tank
                 units[j][i].infantry=FALSE;
                 units[j][i].armor   =30;
                 units[j][i].armored_attack  =40;
                 units[j][i].unarmored_attack=30;
                 units[j][i].accuracy=25;
                 units[j][i].range   =8;
                 units[j][i].movement=24;
                 units[j][i].img     =$IB,"<2>",2$;
                 break;
             case 1: //Medium Tank
                 units[j][i].infantry=FALSE;
                 units[j][i].armor   =60;
                 units[j][i].armored_attack  =60;
                 units[j][i].unarmored_attack=40;
                 units[j][i].accuracy=25;
                 units[j][i].range   =12;
                 units[j][i].movement=16;
                 units[j][i].img     =$IB,"<1>",1$;
                 break;
             case 2: //Standard Rifle Platoon (with bazooka)
                 units[j][i].infantry=TRUE;
                 units[j][i].armor   =0;
                 units[j][i].armored_attack  =15;
                 units[j][i].unarmored_attack=90;
                 units[j][i].accuracy=45;
                 units[j][i].range   =5;
                 units[j][i].movement=4;
                 units[j][i].img     =$IB,"<3>",3$;
                 break;
         }
         do {
             row=RandU4%map_rows;
             col=RandU4%(map_cols/3);
             if (j)
                 col+=2*map_cols/3;
         } while (FindUnit(row,col));
         units[j][i].row=row;
         units[j][i].col=col;
         LBts(&units[j][i].visible[cur_player],0);
     }
}

$BG,11$void NewTurn()
{
 I8 i,j;
 for (j=0;j<2;j++)
     for (i=0;i<MAX_UNITS;i++) {
         units[j][i].remaining_movement=units[j][i].movement;
         units[j][i].fired=FALSE;
     }
 phase=PHASE_START;
 moving_unit=NULL;

 SleepUntil(msg_off_timeout);
 msg_off_timeout=GetTimeStamp+time_stamp_freq*2*ANIMATION_DELAY;
 Sound(1000);
 SPrintF(msg_buf,"Turn %d",++turn);
 RVSetUp(0);
 RVSetUp(1);
 RecalcVisible(RV_ALL_UNITS);
 cur_player=(turn&1)^1;
 enemy_player=cur_player^1;
}


void NewPhase()
{
 cur_player^=1;
 enemy_player=cur_player^1;
 if (++phase>=PHASE_END)
     NewTurn;

 if (phase&~1==PHASE_MOVE)
     Fs->border_attr=WHITE<<4+GREEN;
 else
     Fs->border_attr=WHITE<<4+RED;
 SleepUntil(msg_off_timeout);
 msg_off_timeout=GetTimeStamp+time_stamp_freq*2*ANIMATION_DELAY;
 Sound(1000);
 switch (phase) {
     case PHASE_MOVE0:
     case PHASE_MOVE1:
         SPrintF(msg_buf,"Player %d Move",cur_player+1);
         break;
     case PHASE_FIRE0:
     case PHASE_FIRE1:
         SPrintF(msg_buf,"Player %d Fire",cur_player+1);
         break;
 }
}

void Init()
{
 moving_unit=NULL;
 InitMap;
 view_player=cur_player=0;
 enemy_player=1;
 Fs->horz_scroll.pos=0;
 Fs->vert_scroll.pos=0;
 InitUnits;
 turn=0;
 fire_radius=0;
 show_visible_row=-1;
 show_visible_col=-1;
 *msg_buf=0;
 msg_off_timeout=0;
 phase=PHASE_END;
}

$BG,15$
#define T_TURN_OVER        0
#define T_GAME_OVER        1
#define T_NEW_GAME        2
#define T_EXIT_GAME        3

void CheckUser()
{
 if (!alive_cnt[0] || !alive_cnt[1])
     throw (EXCEPT_LOCAL,T_GAME_OVER);
 switch (ScanChar) {
     case CH_ESC:
     case CH_CTRLQ:
         throw (EXCEPT_LOCAL,T_EXIT_GAME);
     case CH_SPACE:
         throw (EXCEPT_LOCAL,T_TURN_OVER);
     case CH_CR:
         throw (EXCEPT_LOCAL,T_NEW_GAME);
     case '1':
         view_player=0;
         break;
     case '2':
         view_player=1;
         break;
 }
}

void PickAI(I1 *dirname,I8 player)
{
 I8 i=0;
 I1 *st;
 LTDirEntry *tempm,*tempm1,*tempm2;
 Ltf *l=LtfNew;
 BoolI1 *old_silent=Silent(ON);
 st=MSPrintF("%s/*.CPZ",dirname);
 tempm=FindFiles(st);
 Free(st);
 tempm2=FindFiles("HOME/Tanks/*.CPZ");
 tempm1=tempm;
 Silent(old_silent);

 LtfPrintF(l,"Player %d Type\r\n\r\n",player+1);
 while (tempm1) {
     if (!(i++%4))
         LtfPutSExt(l,"\r\n");
     st=StrNew(tempm1->name);
     RemoveLastSeg(st,".");
     tempm1->user_data=LtfPrintF(l,"$$MU-UL,\"%-10ts\",%d$$ ",st,tempm1);
     Free(st);
     tempm1=tempm1->next;
 }
 tempm1=tempm2;
 while (tempm1) {
     if (!(i++%4))
         LtfPutSExt(l,"\r\n");
     st=StrNew(tempm1->name);
     RemoveLastSeg(st,".");
     tempm1->user_data=LtfPrintF(l,"$$MU-UL,\"%-10ts\",%d$$ ",st,tempm1);
     Free(st);
     tempm1=tempm1->next;
 }
 LtfPutSExt(l,"\r\n\r\n\r\nCreate your own AI in HOME/Tanks.");
 while ((tempm1=PopUpMenu(l))<0);
 ExecuteFile(tempm1->full_name);
 LtfDel(l);
 LTDirListDel(tempm);
 LTDirListDel(tempm2);
 st=MSPrintF("move_routines[%d]=&TanksMove;fire_routines[%d]=&TanksFire;",player,player);
 ExecuteStr(st);
 Free(st);
}


$BG,14$void DrawUnit(TssStruct *tss,GrBitMap *base,Unit *tempu,I8 x,I8 y,double f)
{
 if (tempu->infantry)
     GrElemsPlot(base,x-tss->horz_scroll.pos,y-tss->vert_scroll.pos,0,tempu->img);
 else
     GrElemsPlotRotZ(base,x-tss->horz_scroll.pos,y-tss->vert_scroll.pos,0,tempu->img,f);
}

void DrawUnits(TssStruct *tss,GrBitMap *base)
{
 I8 i,j;
 double x,y;
 for (j=0;j<2;j++) {
     if (j)
         base->color=RED;
     else
         base->color=BLUE;
     for (i=0;i<MAX_UNITS;i++) {
         if (units[j][i].life>0 && Bt(&units[j][i].visible[view_player],0)) {
             RowColToXY(x,y,units[j][i].row,units[j][i].col);
             if (phase&~1==PHASE_MOVE && units[j][i].remaining_movement ||
                     phase&~1==PHASE_FIRE && !units[j][i].fired ||
                     Blink(5))
                 DrawUnit(tss,base,&units[j][i],x,y,units[j][i].facing*60.0*pi/180.0);
         }
     }
 }
}

void UpdateWin(TssStruct *tss)
{
 double x,y;
 I8 h,v;
 I8 i,j;
 BoolI1 old_preempt=IsPreempt;
 GrBitMap *base=GrAlias(grbase,tss);

 tss->horz_scroll.min=0;
 tss->horz_scroll.max=map_width-tss->win_pixel_width;
 tss->vert_scroll.min=-FONT_HEIGHT;
 tss->vert_scroll.max=map_height-tss->win_pixel_height;
 UpdateDerivedTssValues(tss);
 h=tss->horz_scroll.pos;
 v=tss->vert_scroll.pos;

 GrBlot(base,-h,-v,map);

 //$FG,2$We want this "atomic" in a multitasking sense.$FG$
 //$BL,14$$FG,2$$LK,"ipx","MN:ipx"$,$LK,"ipy","MN:ipy"$$BL,15$ are the current mouse x,y in screen
coordinates.$FG$
 Preempt(OFF);
 i=ipx-tss->win_pixel_left;
 j=ipy-tss->win_pixel_top;
 if (CursorInWindow(tss,i,j))
     UpdateCursor(tss,i,j);
 RowColToXY(x,y,cursor_row,cursor_col);
 Preempt(old_preempt);

//$FG,2$Roads require multiple cursor fills$FG$
 base->color=YELLOW;
 GrFloodFill(base,x+(HEX_SIDE+dc)/2-h,y-v);
 GrFloodFill(base,x-(HEX_SIDE+dc)/2-h,y-v);
 GrFloodFill(base,x+HEX_SIDE/2-h,y+(HEX_SIDE+dc)/2-v);
 GrFloodFill(base,x+HEX_SIDE/2-h,y-(HEX_SIDE+dc)/2-v);
 GrFloodFill(base,x-HEX_SIDE/2-h,y+(HEX_SIDE+dc)/2-v);
 GrFloodFill(base,x-HEX_SIDE/2-h,y-(HEX_SIDE+dc)/2-v);

 DrawUnits(tss,base);
 if (firing) {
     base->color=BLACK;
     GrCircle(base,fire_x-h,fire_y-v,2);
 }
 if (moving) {
     base->color=YELLOW;
     DrawUnit(tss,base,moving_unit,move_x,move_y,move_facing);
 }
 progress4=progress4_max=progress1=progress1_max=0;
 if (moving_unit) {
     if (ipy<GR_HEIGHT/2) {
         progress4_max=moving_unit->movement;
         progress4=moving_unit->remaining_movement;
     } else {
         progress1_max=moving_unit->movement;
         progress1=moving_unit->remaining_movement;
     }
 }
 if (fire_radius) {
     base->color=YELLOW;
     GrCircle(base,fire_radius_x-h,fire_radius_y-v,fire_radius-1);
     GrCircle(base,fire_radius_x-h,fire_radius_y-v,fire_radius+1);
     base->color=RED;
     GrCircle(base,fire_radius_x-h,fire_radius_y-v,fire_radius);
 }

 //$FG,2$This holds the latest scan code.$FG$
 if (sys_cur_scan_code&SCF_SHIFT) {

     //$FG,2$We want this "atomic" in a multitasking sense.$FG$
     Preempt(OFF);
     if (show_visible_row!=cursor_row || show_visible_col!=cursor_col) {
         show_visible_row=cursor_row;
         show_visible_col=cursor_col;
         Preempt(old_preempt);
         RecalcVisibleMap(show_visible_row,show_visible_col);
     } else
         Preempt(old_preempt);

     base->color=LTGRAY;
     for (j=0;j<map_rows;j++)
         for (i=0;i<map_cols;i++)
             if (!visible_map[j][i]) {
                 RowColToXY(x,y,j,i);
                 GrLine(base,x-6-h,y-6-v,x+6-h,y+6-v);
                 GrLine(base,x+6-h,y-6-v,x-6-h,y+6-v);
                 GrLine(base,x-h,y-6-v,x-h,y+6-v);
                 GrLine(base,x+6-h,y-v,x-6-h,y-v);
             }
 }
 if (i=StrLen(msg_buf)*FONT_WIDTH) {
     base->color=BLACK;
     GrBox(base,(tss->win_pixel_width-i)>>1-10,
                           (tss->win_pixel_height-FONT_HEIGHT)>>1-10,
                           i+20,FONT_HEIGHT+20);

     base->color=YELLOW;
     GrBox(base,(tss->win_pixel_width-i)>>1-7,
                           (tss->win_pixel_height-FONT_HEIGHT)>>1-7,
                           i+14,FONT_HEIGHT+14);

     base->color=RED;
     GrPutS(base,(tss->win_pixel_width-i)>>1,(tss->win_pixel_height-FONT_HEIGHT)>>1,
         msg_buf);
     if (msg_off_timeout) {
         if (msg_off_timeout-GetTimeStamp<3*time_stamp_freq/2*ANIMATION_DELAY)
             Sound(0);
         if (GetTimeStamp>msg_off_timeout)
             *msg_buf=0;
     }
 }

 base->color=WHITE;
 GrBox(base,0,0,25*FONT_WIDTH,FONT_HEIGHT);
 base->color=BLACK;
 GrPrintF(base,0,0,"Turn:%2d Player 1:%3d Player 2:%3d",
     turn,alive_cnt[0],alive_cnt[1]);

 GrDel(base);
}
$BG,15$
void EndTaskCB()
{
 Sound(0);
 progress4=progress4_max=progress1=progress1_max=0;
 Exit;
}

void Tanks()
{
 I8 result,ch;
 I8 old_attr=Fs->border_attr;
 void *old_cur_ltf=Fs->cur_ltf;
 void *old_update=Fs->update_win;
 BoolI1 old_ip_double=Bts(&Fs->display_flags,DISPLAYf_NO_DOUBLE_CLICK);

 map=GrNew(BMT_COLOR4,MAP_WIDTH,MAP_HEIGHT);
 WinMax;
 WordStat(OFF);
 WinBorder(ON);
 Init;
 PickAI("/LT/Apps/Tanks/AIs",0);
 PickAI("/LT/Apps/Tanks/AIs",1);

 PopUpOk("Left-click to move or fire units.\r\n"
                 "$$FG,GREEN$$SPACE$$FG$$ or right-click to end phase.\r\n"
                 "$$FG,GREEN$$SHIFT$$FG$$ to show line-of-sight.\r\n"
                 "$$FG,GREEN$$ENTER$$FG$$ to start new game.\r\n"
                 "$$FG,GREEN$$  1  $$FG$$ Player 1 View.\r\n"
                 "$$FG,GREEN$$  2  $$FG$$ Player 2 View.");
 Fs->end_task_cb=&EndTaskCB; //CTRL-ALT-X
 Fs->update_win=&UpdateWin;
 Fs->cur_ltf=NULL;

 NewPhase;
 try {
     do {
         try {
             result=T_EXIT_GAME;
             if (phase&~1==PHASE_MOVE)
                 CallInd(move_routines[cur_player]);
             else
                 CallInd(fire_routines[cur_player]);
         } catch {
             if (Fs->except_argc==2 && Fs->except_argv[0]==EXCEPT_LOCAL) {
                 Fs->catch_except=TRUE;
                 result=Fs->except_argv[1];
             }
         }
         switch (result) {
             case T_TURN_OVER:
                 NewPhase;
                 break;
             case T_GAME_OVER:
                 while (TRUE) {
                     msg_off_timeout=0;
                     StrCpy(msg_buf,"Game Over");
                     Sound(0);
                     ch=GetChar(NULL,FALSE);
                     if (ch==CH_CR) {
                         Init;
                         NewPhase;
                         break;
                     } else if (ch==CH_ESC || ch==CH_CTRLQ) {
                         result=T_EXIT_GAME;
                         break;
                     } else if (ch=='1')
                         view_player=0;
                     else if (ch=='2')
                         view_player=1;
                 }
                 break;
             case T_NEW_GAME:
                 Init;
                 NewPhase;
                 break;
         }
     } while (result!=T_EXIT_GAME);
 } catch
     Fs->catch_except=TRUE;
 progress4=progress4_max=progress1=progress1_max=0;
 Sound(0);
 Fs->border_attr=old_attr;
 Fs->cur_ltf=old_cur_ltf;
 Fs->update_win=old_update;
 AssignBit(&Fs->display_flags,DISPLAYf_NO_DOUBLE_CLICK,old_ip_double);
 GrDel(map);
}
Home

Flight Simulator
FirstPersonShootr
BigGuns
BirdLand
MultiCore
Tanks
TimeOut
X-Caliber