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);
}