Trivial Solutions Corp. Happy Programmers Make Us Happy The LoseThos 64-bit PC Operating System File:/LT/Apps/Tanks/Tanks.CPZ This, and all LoseThos files, are public domain. Do whatever you like. /* 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 */ #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 /*Constants are often faster when compiled. This is an advantage of just-in-time compilation. You cannot change them when you start a game over, however. A down-side of this is you cannot use the CTRL-C auto-indent feature. */ #exe { TaskStruct *task; PopUpFile("/LT/Demo/Graphics/RotateTank.CPZ",FALSE,NULL,&task); F8 d; if (!PopUpNoYes("Tournament Settings")) { d=PopUpRangeF8Log(300,2000,20,"%4f","Map Width In Pixels\r\n"); StreamPrintF("#define MAP_WIDTH %f\r\n",d); StreamPrintF("#define MAP_HEIGHT %f\r\n",480.0/640.0*d); d=PopUpRangeF8Exp(8,512,2,"%3f","Enemy Units\r\n"); StreamPrintF("#define MAX_UNITS %f\r\n",d); d=PopUpRangeF8(0.2,1.01,0.05,"%4.2f to 1.00","Odds\r\n"); StreamPrintF("#define ODDS %4.2f\r\n",d); d=PopUpRangeF8(0,100,10,"%3f% %%","Friendly Armor Percent\r\n"); StreamPrintF("#define FRIENDLY_ARMOR_PERCENT %f\r\n",d); d=PopUpRangeF8(0,100,10,"%3f% %%","Enemy Armor Percent\r\n"); StreamPrintF("#define ENEMY_ARMOR_PERCENT %f\r\n",d); } d=PopUpRangeF8(0,100,25,"%3f% %%","Animation Delay\r\n"); StreamPrintF("#define ANIMATION_DELAY %5.3f\r\n",d/100.0); XTalk(task," "); TaskWaitIdle(task); Kill(task); }; #define HEX_SIDE 11 F8 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; U1 terrain[map_rows][map_cols]; //Centers of hexes class Point { F8 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]; //Other options for PLAINS are WHITE or YELLOW #define PLAINS LTGREEN #define TREES GREEN #define MOUNTAINS DKGRAY U1 movement_costs[16]; movement_costs[PLAINS]=2; movement_costs[TREES]=6; movement_costs[MOUNTAINS]=10; //These are used to display a range circle when they player //is firing. F8 fire_radius,fire_radius_x,fire_radius_y; //These display "phase", "turn" and "game over". U1 msg_buf[80]; U8 msg_off_timeout; //Goes away after a time. /*I got tricky by not defining a color right away in these GrElems so they can work for both players by setting base->color before drawing them. I actually made these graphics by defining a color in the CTRL-R 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. */ <1>/* Graphics Not Rendered in HTML */ <2>/* Graphics Not Rendered in HTML */ //This is an infantry. <3>/* Graphics Not Rendered in HTML */ class Unit { U1 *img; I8 num,row,col, armored_attack,unarmored_attack,armor; U1 type,player,facing,movement,life, range,remaining_movement,accuracy; BoolI1 visible[2],fired,infantry; }; Unit units[2][MAX_UNITS]; // Bt(visible_unit_bitmap,p1+p0*MAX_UNITS) 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]; U0 Toward(I8 &row,I8 &col,I8 direction) { //We want this "atomic" in a multitasking sense. 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; } U0 RowColToXY(F8 &x,F8 &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; } U0 XYToRowCol(I8 &row,I8 &col,F8 x,F8 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) {//Finds unit in a hexagon. 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(TaskStruct *task,I8 x,I8 y) { if (0<=x<task->win_pixel_width && 0<=y<task->win_pixel_height) return TRUE; else return FALSE; } U0 UpdateCursor(TaskStruct *task,I8 x,I8 y) { //We want this "atomic" in a multitasking sense. BoolI1 old_preempt=Preempt(OFF); if (CursorInWindow(task,x,y)) XYToRowCol(cursor_row,cursor_col,x+task->horz_scroll.pos,y+task->vert_scroll.pos); Preempt(old_preempt); } class LOSCtrl { I8 r1,c1,r2,c2,distance; }; BoolI8 LOSPlot(LOSCtrl *l,I8 x,I8 y,I8 z) { //We got tricky and used z as the distance from the start of the line. 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) return FALSE; } else if (terrain[l->r2][l->c2]==MOUNTAINS) { if (terrain[row][col]==MOUNTAINS || z<=l->distance>>1) return FALSE; } else return FALSE; } return TRUE; } BoolI8 LOS(I8 r1,I8 c1,I8 r2,I8 c2) { F8 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)); return Line(&l,x1,y1,0,x2,y2,l.distance,&LOSPlot); } #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; }; U0 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); LBEqu(&u0->visible[player],0,u0->life>0); } } U0 RVMerge(I8 player) { I8 i,j; Unit *u1; U1 *dst,*src,*mask=CAlloc(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++) LBEqu(&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; F8 x1,y1,x2,y2,d,range; Unit *u0,*u1; 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_F8; 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) LBEqu(&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; } return result; } BoolI8 RecalcVisible(I8 mode,Unit *tempu=NULL) { I8 i,hi,k,cnt; BoolI8 result; /*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. MAlloc() would probably be the better choice. */ 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 DONT_CARE; } cnt=mp_cnt; //cores 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; } 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; }; U0 MPRecalcVisibleMap(MPCtrl2 *job) { I8 i,j; 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; } U0 RecalcVisibleMap(I8 row,I8 col) { I8 i,hi,k,cnt; MPCtrl2 job[mp_cnt]; MPCmdStruct *cmd[mp_cnt]; cnt=mp_cnt; //cores 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; } 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]); } 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,F8 x,F8 y) { I8 direction,best_direction=-1,r,c; F8 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; F8 move_facing; Unit *moving_unit; BoolI8 MovePlot(U0 aux,I8 x,I8 y,I8 z) { nounusedwarn z,aux; move_x=x; move_y=y; Sleep(5*ANIMATION_DELAY); return TRUE; } U0 MoveUnitAnimation(Unit *tempu,I8 r,I8 c,I8 facing) { F8 x1,y1,x2,y2,f=facing*60.0*pi/180.0; moving_unit=tempu; RowColToXY(x1,y1,tempu->row,tempu->col); move_x=x1; move_y=y1; moving=TRUE; if (tempu->infantry) Snd(300); else { move_facing=tempu->facing*60.0*pi/180.0; Snd(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); } Snd(100); } move_facing=f; RowColToXY(x2,y2,r,c); Line(NULL,x1,y1,0,x2,y2,0,&MovePlot); Snd(0); moving_unit=NULL; 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); LBEqu(&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; } BoolI1 firing=FALSE; I8 fire_x,fire_y; BoolI8 FirePlot(U0 aux,I8 x,I8 y,I8 z) { nounusedwarn z,aux; fire_x=x; fire_y=y; firing=TRUE; Sleep(3*ANIMATION_DELAY); return TRUE; } U0 FireShot(Unit *tempu,Unit *target) { I8 r,c,facing,add_modifier, t1=terrain[tempu->row][tempu->col], t2=terrain[target->row][target->col]; F8 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); add_modifier=0; if (t2==TREES) add_modifier+=30; if (t1==MOUNTAINS && t2!=MOUNTAINS) add_modifier-=30; d=(100.0*range_factor/2.0*RandU2/MAX_U2+add_modifier)/tempu->accuracy-1.0; if (d<0) hit=TRUE; else hit=FALSE; if (hit) Noise(500*ANIMATION_DELAY,100,150); else Noise(1000*ANIMATION_DELAY,750,1000); 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) { Noise(1000*ANIMATION_DELAY,1000,4000); 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 (snd_freq) //see Snd() Yield; } U0 DrawHexes() { F8 dx=2*HEX_SIDE+2*dc,dy=2*ds, x,y,x1,y1,x2,y2; I8 i,j; map->color=WHITE; GrRect(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; } } U0 MakeTerrain(U1 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); } } } U0 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; } } } U0 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; } } } U0 DrawTerrain() { I8 i,j; F8 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); } } U0 DrawRivers() { I8 i,j,k,r,c; F8 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); } } } } } U0 DrawRoads() { I8 i,j,k,r,c; F8 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); } } } } } U0 CalcHexCenters() { I8 i,j; F8 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; } } U0 DrawDots() { I8 i,j; F8 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); } } U0 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; } U0 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 =<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 =<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 =<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); } } U0 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; Snd(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; } U0 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; Snd(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; } } U0 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; } #define T_TURN_OVER 0 #define T_GAME_OVER 1 #define T_NEW_GAME 2 #define T_EXIT_GAME 3 U0 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; } } U0 PickAI(U1 *dirname,I8 player) { I8 i=0; U1 *st; LTDirEntry *tempm,*tempm1,*tempm2; Ltf *l=LtfNew; BoolI1 *old_silent=Silent(ON); st=MSPrintF("%s/*.CPZ",dirname); tempm=FilesFind(st); Free(st); tempm2=FilesFind("HOME/Tanks/*.CPZ"); tempm1=tempm; Silent(old_silent); LtfPrintF(l,"Player %d Type\r\n\r\n",player+1); while (tempm1) { if (!(i++%4)) LtfPutS(l,"\r\n"); st=StrNew(tempm1->name); StrLastRem(st,"."); tempm1->user_data=LtfPrintF(l,"$$MU-UL,\"%-10ts\",%d$$ ",st,tempm1); Free(st); tempm1=tempm1->next; } tempm1=tempm2; while (tempm1) { if (!(i++%4)) LtfPutS(l,"\r\n"); st=StrNew(tempm1->name); StrLastRem(st,"."); tempm1->user_data=LtfPrintF(l,"$$MU-UL,\"%-10ts\",%d$$ ",st,tempm1); Free(st); tempm1=tempm1->next; } LtfPutS(l,"\r\n\r\n\r\nCreate your own AI in HOME/Tanks."); while ((tempm1=PopUpMenu(l))<0); ExeFile(tempm1->full_name); LtfDel(l); LTDirListDel(tempm); LTDirListDel(tempm2); ExePrintF("move_routines[%d]=&TanksMove;fire_routines[%d]=&TanksFire;",player,player); } U0 DrawUnit(TaskStruct *task,GrBitMap *base,Unit *tempu,I8 x,I8 y,F8 f) { if (tempu->infantry) GrElemsPlot(base,x-task->horz_scroll.pos,y-task->vert_scroll.pos,0,tempu->img); else GrElemsPlotRotZ(base,x-task->horz_scroll.pos,y-task->vert_scroll.pos,0,tempu->img,f); } U0 DrawUnits(TaskStruct *task,GrBitMap *base) { I8 i,j; F8 x,y; Unit *tempu; for (j=0;j<2;j++) { if (j) base->color=LTPURPLE; else base->color=LTCYAN; for (i=0;i<MAX_UNITS;i++) { tempu=&units[j][i]; if (tempu->life>0 && Bt(&tempu->visible[view_player],0) && tempu!=moving_unit) { RowColToXY(x,y,tempu->row,tempu->col); if (phase&~1==PHASE_MOVE && tempu->remaining_movement || phase&~1==PHASE_FIRE && !tempu->fired || Blink(5,tP(task))) DrawUnit(task,base,tempu,x,y,tempu->facing*60.0*pi/180.0); } } } } U0 UpdateWin(TaskStruct *task) { F8 x,y; I8 h,v; I8 i,j; BoolI1 old_preempt=IsPreempt; GrBitMap *base=GrAlias(gr_refreshed_base,task); task->horz_scroll.min=0; task->horz_scroll.max=map_width-task->win_pixel_width; task->vert_scroll.min=-FONT_HEIGHT; task->vert_scroll.max=map_height-task->win_pixel_height; TaskDerivedValuesUpdate(task); h=task->horz_scroll.pos; v=task->vert_scroll.pos; GrBlot(base,-h,-v,map); //We want this "atomic" in a multitasking sense. //ipx,ipy are the current mouse x,y in screen coordinates. Preempt(OFF); i=ipx-task->win_pixel_left; j=ipy-task->win_pixel_top; if (CursorInWindow(task,i,j)) UpdateCursor(task,i,j); RowColToXY(x,y,cursor_row,cursor_col); Preempt(old_preempt); //Roads require multiple cursor fills 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(task,base); if (firing) { base->color=BLACK; GrCircle(base,fire_x-h,fire_y-v,2); } if (moving_unit && moving_unit->visible[view_player]) { base->color=YELLOW; DrawUnit(task,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); } if (Bt(key_down_bitmap,SC_SHIFT)) { //We want this "atomic" in a multitasking sense. 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; GrRect(base,(task->win_pixel_width-i)>>1-10, (task->win_pixel_height-FONT_HEIGHT)>>1-10, i+20,FONT_HEIGHT+20); base->color=YELLOW; GrRect(base,(task->win_pixel_width-i)>>1-7, (task->win_pixel_height-FONT_HEIGHT)>>1-7, i+14,FONT_HEIGHT+14); base->color=RED; GrPutS(base,(task->win_pixel_width-i)>>1,(task->win_pixel_height-FONT_HEIGHT)>>1, msg_buf); if (msg_off_timeout) { if (msg_off_timeout-GetTimeStamp<3*time_stamp_freq/2*ANIMATION_DELAY) Snd(0); if (GetTimeStamp>msg_off_timeout) *msg_buf=0; } } base->color=WHITE; GrRect(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); } U0 TaskEndCB() { Snd(0); progress4=progress4_max=progress1=progress1_max=0; Exit; } U0 Tanks() { U8 result,ch; map=GrNew(BMT_COLOR4,MAP_WIDTH,MAP_HEIGHT); SettingsPush; //See SettingsPush LBts(&Fs->display_flags,DISPLAYf_NO_DBL_CLICK); MenuPush( "File {" " Abort(,CH_CTRLQ);" " Exit(,CH_ESC);" "}" "Play {" " EndPhase(,CH_SPACE);" " Restart(,CH_CR);" "}" "View {" " Player1(,'1');" " Player2(,'2');" " LOS(,0,SCF_SHIFT);" "}" ); 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->task_end_cb=&TaskEndCB; //CTRL-ALT-X Fs->update_win=&UpdateWin; LtfClear; 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"); Snd(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; SettingsPop; MenuPop; GrDel(map); }