/* Octagon Tank Battle game by David Coffin 9/15/1989 Written for the Hercules Graphics Card 5/28/2018 Ported to Linux/X11 */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include /* My custom monochrome frame-buffer code. Originally written for the Hercules Graphics Card (720x348, 3:4 pixel aspect ratio) 720x464 achieves the same display aspect ratio with square pixels. */ #define COLOR 0xffbf00 // amber, like on my Leading Edge Model D #define WIDE 720 #define HIGH 464 #define CORNER ((int)((HIGH-1)/(2+M_SQRT2)+0.5)) #define AREA (WIDE*HIGH - 2*CORNER*CORNER) #define PAD 32 #if (WIDE % 8) #error WIDE must be a multiple of 8!! #endif #if !defined(uchar) #define uchar unsigned char #endif #if !defined(ushort) #define ushort unsigned short #endif /* Simple monochrome frame buffer: page 0 -- active drawing area page 1 -- background of non-moving objects page 2 -- last image sent to X server page 3 -- solid octagons for collision detection */ uchar fbuf[4][HIGH][WIDE/8]; #define INS(x,y) ((unsigned)(x) < WIDE && (unsigned)(y) < HIGH) #define POINTON(page,x,y) fbuf[page][y][(x)>>3] |= 1 << ((x) & 7) #define POINTOFF(page,x,y) fbuf[page][y][(x)>>3] &= ~(1 << ((x) & 7)) #define TOGGLE(page,x,y) fbuf[page][y][(x)>>3] ^= 1 << ((x) & 7) #define GETPOINT(page,x,y) (fbuf[page][y][(x)>>3] >> (x & 7) & 1) #define SWAP(a,b) { a=a+b; b=a-b; a=a-b; } void putpixel (int page, int x, int y, int val) { if (!INS(x,y)) return; if (val) POINTON(page,x,y); else POINTOFF(page,x,y); } void clearrect (int page, int xs, int ys, int xf, int yf) { uchar row[WIDE/8]; int *clip[] = { &xs, &ys, &xf, &yf }; int clim[] = { WIDE-1,HIGH-1,WIDE-1,HIGH-1 }; int xsb, xfb, x, y; if (xs > xf) SWAP(xs,xf); if (ys > yf) SWAP(ys,yf); for (x=0; x < 4; x++) { if (*clip[x] < 0) *clip[x] = 0; if (*clip[x] > clim[x]) *clip[x] = clim[x]; } memset(row,255,sizeof row); xsb = xs >> 3; xfb = xf >> 3; for (x = xsb; x <= xfb; x++) row[x]=0; row[xsb] |= ~(254 << (xs & 7)); row[xfb] |= 254 << (xf & 7); for (y = ys; y <= yf; y++) for (x = xsb; x <= xfb; x++) fbuf[page][y][x] &= row[x]; } void outtextxy (int page, int x, int y, char *st) { /* Taken from the 8x13 X11 font */ static const uchar font[95][11] = { { 0,0,0,0,0,0,0,0,0,0,0 }, { 8,8,8,8,8,8,8,0,8,0,0 }, { 36,36,36,0,0,0,0,0,0,0,0 }, { 0,36,36,126,36,126,36,36,0,0,0 }, { 8,60,10,10,28,40,40,30,8,0,0 }, { 68,74,36,16,16,8,36,84,34,0,0 }, { 0,0,12,18,18,12,82,34,92,0,0 }, { 8,8,8,0,0,0,0,0,0,0,0 }, { 32,16,16,8,8,8,16,16,32,0,0 }, { 4,8,8,16,16,16,8,8,4,0,0 }, { 36,24,126,24,36,0,0,0,0,0,0 }, { 0,0,8,8,62,8,8,0,0,0,0 }, { 0,0,0,0,0,0,0,28,12,2,0 }, { 0,0,0,0,62,0,0,0,0,0,0 }, { 0,0,0,0,0,0,0,8,28,8,0 }, { 64,64,32,16,8,4,2,1,1,0,0 }, { 24,36,66,66,66,66,66,36,24,0,0 }, { 8,12,10,8,8,8,8,8,62,0,0 }, { 60,66,66,64,32,24,4,2,126,0,0 }, { 126,64,32,16,56,64,64,66,60,0,0 }, { 32,48,40,36,34,34,126,32,32,0,0 }, { 126,2,2,58,70,64,64,66,60,0,0 }, { 56,4,2,2,58,70,66,66,60,0,0 }, { 126,64,32,16,16,8,8,4,4,0,0 }, { 60,66,66,66,60,66,66,66,60,0,0 }, { 60,66,66,98,92,64,64,32,28,0,0 }, { 0,0,8,28,8,0,0,8,28,8,0 }, { 0,0,8,28,8,0,0,28,12,2,0 }, { 64,32,16,8,4,8,16,32,64,0,0 }, { 0,0,0,126,0,0,126,0,0,0,0 }, { 2,4,8,16,32,16,8,4,2,0,0 }, { 60,66,66,64,32,16,16,0,16,0,0 }, { 60,66,66,114,74,106,82,2,60,0,0 }, { 24,36,66,66,66,126,66,66,66,0,0 }, { 30,34,66,34,30,34,66,34,30,0,0 }, { 60,66,2,2,2,2,2,66,60,0,0 }, { 30,34,66,66,66,66,66,34,30,0,0 }, { 126,2,2,2,30,2,2,2,126,0,0 }, { 126,2,2,2,30,2,2,2,2,0,0 }, { 60,66,2,2,2,114,66,98,92,0,0 }, { 66,66,66,66,126,66,66,66,66,0,0 }, { 62,8,8,8,8,8,8,8,62,0,0 }, { 120,32,32,32,32,32,32,34,28,0,0 }, { 66,34,18,10,6,10,18,34,66,0,0 }, { 2,2,2,2,2,2,2,2,126,0,0 }, { 65,65,99,85,73,73,65,65,65,0,0 }, { 66,66,70,74,82,98,66,66,66,0,0 }, { 60,66,66,66,66,66,66,66,60,0,0 }, { 62,66,66,66,62,2,2,2,2,0,0 }, { 60,66,66,66,66,66,74,82,60,64,0 }, { 62,66,66,66,62,10,18,34,66,0,0 }, { 60,66,2,2,60,64,64,66,60,0,0 }, { 127,8,8,8,8,8,8,8,8,0,0 }, { 66,66,66,66,66,66,66,66,60,0,0 }, { 65,65,34,34,34,20,20,20,8,0,0 }, { 65,65,65,65,73,73,73,85,34,0,0 }, { 65,65,34,20,8,20,34,65,65,0,0 }, { 65,65,34,20,8,8,8,8,8,0,0 }, { 126,64,32,16,8,4,2,2,126,0,0 }, { 60,4,4,4,4,4,4,4,60,0,0 }, { 1,1,2,4,8,16,32,64,64,0,0 }, { 30,16,16,16,16,16,16,16,30,0,0 }, { 8,20,34,0,0,0,0,0,0,0,0 }, { 0,0,0,0,0,0,0,0,0,127,0 }, { 8,16,0,0,0,0,0,0,0,0,0 }, { 0,0,0,60,64,124,66,98,92,0,0 }, { 2,2,2,58,70,66,66,70,58,0,0 }, { 0,0,0,60,66,2,2,66,60,0,0 }, { 64,64,64,92,98,66,66,98,92,0,0 }, { 0,0,0,60,66,126,2,66,60,0,0 }, { 56,68,4,4,62,4,4,4,4,0,0 }, { 0,0,0,92,34,34,28,2,60,66,60 }, { 2,2,2,58,70,66,66,66,66,0,0 }, { 0,8,0,12,8,8,8,8,62,0,0 }, { 0,32,0,48,32,32,32,32,34,34,28 }, { 2,2,2,34,18,14,18,34,66,0,0 }, { 12,8,8,8,8,8,8,8,62,0,0 }, { 0,0,0,55,73,73,73,73,65,0,0 }, { 0,0,0,58,70,66,66,66,66,0,0 }, { 0,0,0,60,66,66,66,66,60,0,0 }, { 0,0,0,58,70,66,70,58,2,2,2 }, { 0,0,0,92,98,66,98,92,64,64,64 }, { 0,0,0,58,68,4,4,4,4,0,0 }, { 0,0,0,60,66,12,48,66,60,0,0 }, { 0,4,4,62,4,4,4,68,56,0,0 }, { 0,0,0,34,34,34,34,34,92,0,0 }, { 0,0,0,34,34,34,20,20,8,0,0 }, { 0,0,0,65,65,73,73,85,34,0,0 }, { 0,0,0,66,36,24,24,36,66,0,0 }, { 0,0,0,66,66,66,98,92,64,66,60 }, { 0,0,0,126,32,16,8,4,126,0,0 }, { 112,8,8,16,12,16,8,8,112,0,0 }, { 8,8,8,8,8,8,8,8,8,0,0 }, { 14,16,16,8,48,8,16,16,14,0,0 }, { 36,42,18,0,0,0,0,0,0,0,0 }, }; uchar c, i; if (y < 0 || y+11 > HIGH) return; x >>= 3; while ((c = *st++)) { if ((unsigned)(c -= 32) < 95) for (i=0; i < 11; i++) fbuf[page][y+i][x] = font[c][i]; x++; } } /* Not much X11 code here, since I draw everything in my own frame buffer and push it to the X server. */ Display *dis; int screen, exposed; Window win; XImage *ximage; GC gc; void init_x() { dis = XOpenDisplay(0); screen = DefaultScreen (dis); win = XCreateSimpleWindow (dis, DefaultRootWindow(dis),0,0, WIDE+PAD*2, HIGH+PAD*2, 5, 0, 0); XSetStandardProperties (dis, win, "OCTAGON","OCTAGON",None,0,0,0); XSelectInput (dis, win, ExposureMask|KeyPressMask); gc = XCreateGC (dis, win, 0,0); XClearWindow (dis, win); XMapRaised (dis, win); ximage = XCreateImage (dis, DefaultVisual(dis,screen), 1, XYBitmap, 0, (char *)fbuf[0][0], WIDE, HIGH, 8, WIDE/8); } /* Now the actual game. Compute all motion in 1/256 pixels. The center of screen pixel (0,0) is (128,128) */ #define ALIVE 250 #define HRAD ((WIDE-4) << 7) #define VRAD ((HIGH-2) << 7) #define DRAD ((int)(((WIDE-4)+(HIGH-2)/(1+M_SQRT2))*128)) struct Shot { uchar ismine, age, explo, dir; int sposx, sposy, velx, vely; struct Shot *next; } *shotbase; typedef struct Shot shot; const int movx[9] = { 0,46,65,46,0,-46,-65,-46,0 }; const int movy[9] = { -65,-46,0,46,65,46,0,-46,-65 }; char name[2][32]; struct { int tposx, tposy, velx, vely; int speed, turnsp, front, aim, ammo, life, model; } tank[2], prevtank[2]; unsigned score[3] = { 0,0,0 }; struct fragment { int px, py, vx, vy; } shrap [2][80]; void memerror() { puts("The heap is full!"); exit(0); } char infield (int x, int y) { int ax, ay; ax = abs (x - (WIDE << 7)); ay = abs (y - (HIGH << 7)); return ax < HRAD && ay < VRAD && ax+ay < DRAD && !GETPOINT(3,x>>8,y>>8); } void laymines(int num) { char mshape[] = { -1,-2, 0,-2, 1,-2, 2,-1, 2,0, 2,1, 1,2, 0,2, -1,2, -2,1, -2,0, -2,-1 }; int scx, scy, i; shot *p; while (shotbase) /* Clear all bullets and mines */ { p = shotbase; shotbase = shotbase->next; free(p); } while (num--) { if (!(p=(shot *) malloc(sizeof (shot)))) memerror(); p->next=shotbase; shotbase=p; do { /* Search for open spaces to lay mines */ p->sposx = random() % (WIDE << 8); p->sposy = random() % (HIGH << 8); p->ismine=1; p->age=1; p->explo=0; } while (!infield(p->sposx,p->sposy)); scx = p->sposx >> 8; scy = p->sposy >> 8; for (i=0; i < sizeof(mshape); i+=2) putpixel (0, scx+mshape[i], scy+mshape[i+1],1); } } void make_octagon() { int top, left, diam, corn, ax, ay, y, xs, xf, x; do { left = random() % WIDE; top = random() % HIGH; diam = random() % 40 + 10; corn = diam/(2 + M_SQRT2) + 0.5; ax = abs (left*2 + diam - (WIDE-1)); ay = abs ( top*2 + diam - (HIGH-1)); } while (ax > WIDE-diam || ay > HIGH-diam || ax + ay > WIDE+HIGH-2-2*CORNER - diam*(1+1/(1+M_SQRT2))); for (y=0; y <= diam; y++) { xs = corn+y - diam; if (xs < corn-y) xs = corn-y; if (xs < 0) xs = 0; xf = diam - xs; for (x=xs; x <= xf; x++) { putpixel (3,left+x,top+y,1); if (y == 0 || y == diam || x == xs || x == xf || (left+x + 2*(top+y) & 3)) putpixel (0,left+x,top+y,1); } } } void show_num (int page, int x, int y, int num) { char snum[16]; sprintf (snum, "%d", num); clearrect (page,x,y,x+30,y+10); outtextxy (page,x,y,snum); } void show_nums (int page) { show_num (page,56,14,score[0]); show_num (page,56,28,tank[0].ammo); show_num (page,56,HIGH-14,score[2]); show_num (page,WIDE-24,14,score[1]); show_num (page,WIDE-24,28,tank[1].ammo); } void setupfield() { int cenx, ceny, rad, inf, i, j; float ang; exposed = 1; memset (fbuf, 0, sizeof fbuf); for (i=0; i < (AREA + 3714)/7428; i++) make_octagon(); for (i = CORNER; i < WIDE-CORNER; i++) { putpixel (0,i,0,1); putpixel (0,i,HIGH-1,1); } for (i=0; i < HIGH; i++) { j = CORNER - HIGH + 1 + i; if (j < CORNER-i) j = CORNER-i; if (j < 0) j = 0; putpixel (0,j,i,1); putpixel (0,j+1,i,1); putpixel (0,WIDE-j-2,i,1); putpixel (0,WIDE-j-1,i,1); } for (i=1; i < HIGH; i+=2) { /* Draw territory lines */ putpixel (0, CORNER+1, i, 1); putpixel (0, WIDE-2-CORNER, i, 1); } laymines((AREA+12400)/24800); outtextxy (0,0,0,name[0]); outtextxy (0,WIDE-strlen(name[1])*8,0,name[1]); outtextxy (0,0,14,"SCORE:"); outtextxy (0,WIDE-80,14,"SCORE:"); outtextxy (0,8,HIGH-14,"TIES:"); outtextxy (0,8,28,"AMMO:"); outtextxy (0,WIDE-72,28,"AMMO:"); memcpy (fbuf[1], fbuf[0], sizeof *fbuf); } char hit (int ix,int iy) /* Tells if a tank is hit */ { int t,h; long x=ix, y=iy; for (t=0,h=0; t<2; t++) { if (tank[t].life == ALIVE) { if (tank[t].front & 1) { if (labs(tank[t].tposx-x) < 1152 && labs(tank[t].tposy-y) < 1152) h=t+1; } else if (labs(tank[t].tposx-x)+labs(tank[t].tposy-y) < 1629) h=t+1; } } return h; } char tankinfield(int t) { /* Tell if tank #I is within the walls */ int cornx[2][4]={ { -1152,1152,1152,-1152 }, { 1629,0,-1629,0 } }; int corny[2][4]={ { -1152,-1152,1152,1152 }, { 0,-1629,0,1629 } }; uchar c, inf, tilt = tank[t].front & 1; for (c=0,inf=1; c<4; c++) inf=inf && infield(tank[t].tposx+cornx[tilt][c], tank[t].tposy+corny[tilt][c]); inf=inf && infield(tank[t].tposx+45*movx[tank[t].aim], tank[t].tposy+45*movy[tank[t].aim]); return inf; } void setuptanks() { int bad, i, t; for (t=0; t < 2; t++) { switch (tank[t].model) { case 'B':tank[t].turnsp=2; tank[t].front=1; break; case 'R':tank[t].turnsp=2; tank[t].front=0; break; case 'Q':tank[t].turnsp=1; tank[t].front=0; } tank[t].velx = tank[t].vely = tank[t].speed = 0; tank[t].life = ALIVE; tank[t].aim = tank[t].front; tank[t].ammo = 60; do { shot *sh; bad = 0; tank[t].tposx = random() % (int)((HIGH << 8)/(2+M_SQRT2)) - 1500; tank[t].tposy = random() % (HIGH << 8); if (t) tank[t].tposx = (WIDE << 8) - tank[t].tposx; for (sh=shotbase; sh; sh=sh->next) /* Don't set the tank on a mine! */ bad=bad || hit(sh->sposx,sh->sposy); for (i=0; i < 2; i++) { tank[t].front ^= 1; bad |= !tankinfield(t); } } while (bad); prevtank[t] = tank[t]; } } void velocity(int t) /* Compute the tank's velocity */ { tank[t].velx = tank[t].speed*movx[tank[t].front]; tank[t].vely = tank[t].speed*movy[tank[t].front]; } /* Use scancodes, not key letters, because we only care about the physical location of these keys. */ int keycheck() { XEvent xev; char key; int pl, escape=0; shot *news; while (XCheckWindowEvent (dis, win, ExposureMask|KeyPressMask, &xev)) { if (xev.type == Expose) { exposed = 1; continue; } key = xev.xkey.keycode; switch (key) { case 24: case 25: case 26: case 38: case 39: case 40: case 52: case 53: pl=0; break; case 32: case 33: case 34: case 46: case 47: case 48: case 60: case 61: pl=1; break; case 9: escape = 1; default: continue; } if (tank[pl].life != ALIVE) continue; switch (key) { case 24: case 32: /* Gun left */ tank[pl].aim -= tank[pl].turnsp; if (tank[pl].aim < 0) tank[pl].aim += 8; break; case 26: case 34: /* Gun right */ tank[pl].aim += tank[pl].turnsp; if (tank[pl].aim > 7) tank[pl].aim -= 8; break; case 25: case 33: /* Fire gun */ if (tank[pl].ammo) { tank[pl].ammo--; if (!(news=(shot *) malloc(sizeof (shot)))) memerror(); news->sposx = tank[pl].tposx; news->sposy = tank[pl].tposy; news->velx = tank[pl].velx/2 + movx[tank[pl].aim]*8; news->vely = tank[pl].vely/2 + movy[tank[pl].aim]*8; news->age=60; news->explo=0; news->ismine=0; news->dir = tank[pl].aim; news->next = shotbase; shotbase=news; } break; case 38: case 46: /* Turn left */ tank[pl].front = (tank[pl].front - tank[pl].turnsp) & 7; velocity(pl); break; case 40: case 48: /* Turn right */ tank[pl].front = (tank[pl].front + tank[pl].turnsp) & 7; velocity(pl); break; case 39: case 47: /* Speed up */ if (++tank[pl].speed > 3) tank[pl].speed=3; velocity(pl); break; case 53: case 61: /* Slow down */ if (--tank[pl].speed < 0) tank[pl].speed=0; velocity(pl); break; case 52: case 60: /* Drive in reverse */ if (!tank[pl].speed) { tank[pl].speed=-1; velocity(pl); } } } return escape; } void update() { int i,t; shot *sh, *prev; for (t=0; t < 2; t++) /* Update tank positions */ { tank[t].tposx+=tank[t].velx; tank[t].tposy+=tank[t].vely; if (!tankinfield(t)) { tank[t]=prevtank[t]; tank[t].speed=tank[t].velx=tank[t].vely=0; } prevtank[t]=tank[t]; } sh=shotbase; while (sh) { if (sh->ismine) { if ((t=hit(sh->sposx,sh->sposy))) sh->age=0; } else { if (sh->age--) { for (i=0; i < 2; i++) { /* Move the bullet twice */ t = hit(sh->sposx, sh->sposy); if (sh->age > 57) t=0; sh->sposx += sh->velx; sh->sposy += sh->vely; if (!infield(sh->sposx, sh->sposy) || t) { sh->sposx -= sh->velx; /* The shot hits a wall */ sh->sposy -= sh->vely; sh->age = 0; break; } } } else sh->velx = sh->vely = 0; } if (t-- && !sh->explo) { /* A tank is hit! */ tank[t].life--; /* Set the tank on fire */ tank[t].velx=tank[t].vely=0; } if (!sh->age && !sh->explo) sh->explo=1; if (sh->explo > 25) /* Remove the shot from the list */ if (sh==shotbase) { shotbase = sh->next; free(sh); sh = shotbase; } else { prev->next = sh->next; free(sh); sh = prev->next; } else { /* Go on to the next shot */ prev = sh; sh = sh->next; } } } void putbits (int xoff, int yoff, const ushort *clear, const ushort *set, int size) { ushort xoffm8, *scr; xoffm8 = xoff & 7; for (; size; size--, set++) { scr = (ushort *) &fbuf[0][yoff++][xoff >> 3]; if (clear) { scr[0] &= ~(*clear << xoffm8); scr[1] &= ~(*clear >> (16-xoffm8)); clear++; } scr[0] |= *set << xoffm8; scr[1] |= *set >> (16-xoffm8); } } void putpic (int scx, int scy, const signed char *pic) { for (; *pic!=-128; pic+=2) putpixel (0, scx+pic[0], scy+pic[1], 1); } void eight_line (int x, int y, int dir, int len) { static const signed char dxy[2][8] = { { 0,1,1,1,0,-1,-1,-1 }, { -1,-1,0,1,1,1,0,-1 } }; if (dir & 1) len = len/M_SQRT2 + 0.5; while (len--) { putpixel (0, x, y, 1); x += dxy[0][dir]; y += dxy[1][dir]; } } void draw_frame() { static const ushort tankpat[10][15] = { { 0,0,0,0,8184,8184,8184,8184,8184,8184,8184,8184,8184,0,0 }, { 0,128,448,992,2032,4088,8188,16382,16382,8188,4088,2032,992,448,128 }, { 0,0,3096,4088,3096,3096,3096,3096,3096,3096,3096,4088,0,0,0 }, { 256,896,448,736,1136,10296,28700,14350,7172,3592,1808,928,448,128,0 }, { 0,0,0,8184,8184,2056,2056,2056,2056,2056,8184,8184,0,0,0 }, { 0,128,448,928,1808,3592,7172,14350,28700,10296,1136,736,448,896,256 }, { 0,0,0,4088,3096,3096,3096,3096,3096,3096,3096,4088,3096,0,0 }, { 0,128,448,736,1136,2104,4124,14350,7175,3594,1808,928,448,224,64 }, { 0,0,0,4092,4092,2056,2056,2056,2056,2056,4092,4092,0,0,0 }, { 64,224,448,928,1808,3594,7175,14350,4124,2104,1136,736,448,128,0 } }; static const signed char explosion[8][52] = { { -2,-2,0,-2,2,-2,-1,-1,0,-1,1,-1,-3,0,-2,0,-1,0,0,0,1,0,2,0,3, 0,-1,1,0,1,1,1,-2,2,0,2,2,2,0,-3,0,3,-128,-128 }, { 0,0,0,-1,0,1,-1,0,1,0,1,-2,-1,2,2,-1,-2,1,3,0,-3,0,2,1,0,5,0,-5, -2,-1,1,2,-1,-2,0,3,0,-3,3,-3,-3,3,5,0,-5,0,3,3,-3,-3,-128,-128 }, { -1,-2,1,-2,-2,-1,0,-1,2,-1,-3,0,-1,0,0,-3,0,3,1,0,3,0, -2,1,0,1,2,1,-1,2,1,2,-128,-128 }, { -1,-3,1,-3,-3,-1,3,-1,-1,0,1,0,-3,1,3,1,-1,3,1,3,-128,-128 }, { 0,-4,0,4,-4,0,4,0,-128,-128 } }; static const signed char check[] = { 0,-2,-1,-1,1,-1,-2,0,2,0,-1,1,1,1,0,2,-128,-128 }; shot *sh; int t,w,x,y,scx,scy; show_nums(1); memcpy (fbuf[0], fbuf[1], sizeof *fbuf); for (t=0; t<2; t++) { scx = tank[t].tposx >> 8; scy = tank[t].tposy >> 8; if (tank[t].life > ALIVE-15) { putbits(scx-7,scy-7,tankpat[tank[t].front & 1], tankpat[tank[t].front+2],15); eight_line (scx, scy, tank[t].aim, 12); if (!t) putpic(scx,scy,check); } else if (tank[t].life > 0) { /* Draw a burning tank */ if (tank[t].life % 5 == 0) if (tank[t].front & 1) for (y = scy-6; y <= scy+6; y++) for (w=6-abs(scy-y), x=scx-w; x <= scx+w; x++) putpixel (1, x, y, random() & 1); else for (y = scy-4; y <= scy+4; y++) for (x = scx-4; x <= scx+4; x++) putpixel (1, x, y, random() & 1); if (tank[t].life > ALIVE-55) { /* Draw the flying debris */ struct fragment *fr; if (tank[t].life == ALIVE-15) { /* Start the explosion */ int i, speed; float ang; for (i=0; i<80; i++) { shrap[t][i].px = tank[t].tposx; shrap[t][i].py = tank[t].tposy; speed = (random()%24) * (random()%24) + 43; ang = random()%1024 * (M_PI/512); shrap[t][i].vx = speed*sin(ang); shrap[t][i].vy = speed*cos(ang); } } if (tank[t].life==ALIVE-50) { /* Draw the fragments permanently */ for (fr=shrap[t]; frpx >> 8, fr->py >> 8); } else { /* Pencil the flying pieces */ for (fr=shrap[t]; frpx >> 8, fr->py >> 8); fr->px+=fr->vx; fr->py+=fr->vy; if (!infield(fr->px,fr->py)) { /* A piece hits the wall */ fr->px -= fr->vx; fr->py -= fr->vy; fr->vx = fr->vy = 0; } } } } } if (tank[t].life < ALIVE) tank[t].life--; } sh=shotbase; while (sh) { scx = sh->sposx >> 8; scy = sh->sposy >> 8; if (sh->explo) putpic(scx,scy,explosion[(sh->explo++ - 1)/5]); else if (!sh->ismine) eight_line (scx, scy, sh->dir^4, 5); sh=sh->next; } } void display_x () { unsigned *old, *new, i, b, mask, pix, c, last_c=-1; if (exposed) { XSetForeground (dis, gc, COLOR); XPutImage (dis, win, gc, ximage, 0, 0, PAD, PAD, WIDE, HIGH); memcpy (fbuf[2], fbuf[0], sizeof *fbuf); exposed = 0; } else { old = (unsigned *) fbuf[2]; new = (unsigned *) fbuf[0]; for (i=0; i < HIGH*WIDE/32; i++) if ((mask = new[i] ^ old[i])) { for (b=0; b < 32; b++) if (mask & (1 << b)) { pix = i << 5 | b; c = new[i] >> b & 1; if (c != last_c) XSetForeground (dis, gc, COLOR * (last_c = c)); XDrawPoint (dis, win, gc, pix%WIDE+PAD, pix/WIDE+PAD); } old[i] = new[i]; } } } char who_won() /* 0=Game in progress */ /* 1=Player 0 wins */ /* 2=Player 1 wins */ /* 3=Both players killed */ { int t; if (tank[0].life < -150 && tank[1].life < -150) return 3; else for (t=0; t<2; t++) if (tank[!t].life < -150 && tank[t].life == ALIVE) return t+1; return 0; } void overwrite (int x, int y, char *st) { x &= -8; clearrect (0,x-4,y-4,x+strlen(st)*8,y+15); outtextxy (0,x,y,st); display_x(); } char getchar_x() { XEvent xev; KeySym key; char text[256]; XWindowEvent (dis, win, KeyPressMask, &xev); XLookupString (&xev.xkey, text, 256, &key, 0); return text[0]; } int main() { char winner, anoth, newf, buf[32]; int i; shotbase = 0; srandom(time(0)); puts("\n\t\t OCTAGON TANK BATTLE"); puts(""); puts("\t\t\tby David Coffin\n"); puts(" The object of Octagon is to outmanuever your opponent and"); puts("shoot him. You may choose one of three tanks. The Bishop"); puts("tank can move and fire only in diagonal directions. The Rook"); puts("tank moves and fires orthogonally. The Queen can move or fire"); puts("in any direction."); for (i=0; i<2; i++) { char m; printf("\nWhat is player %d\'s name? ",i+1); fgets (name[i],sizeof name[i],stdin); *strchrnul(name[i],'\n') = 0; do { printf("\r%s, what type of tank do you want (B,R,Q)? ",name[i]); fgets (buf, sizeof buf, stdin); tank[i].model=m=toupper(buf[0]); } while (m!='B' && m!='R' && m!='Q'); } puts("\nKEYBOARD CONTROLS:\n"); printf("%12s PURPOSE %12s\n",name[0],name[1]); puts("[Q] [W] [E] \\ GL FG GR \\ [O] [P] [[]"); puts(" [A] [S] [D] \\ TL SU TR \\ [L] [;] [\"]"); puts(" [Z] [X] \\ RV SD \\ [.] [/]\n"); puts("GL: Gun Left FG: Fire Gun GR: Gun Right"); puts(" TL: Turn Left SU: Speed Up TR: Turn Right"); puts(" RV: Reverse SD: Slow Down\n"); init_x(); setupfield(); while (1) { winner = 0; setuptanks(); do { if (keycheck()) goto escape; update(); draw_frame(); display_x(); usleep (12000); } while (!(winner = who_won())); score[winner-1]++; show_nums(0); if (winner == 3) overwrite (CORNER+64, 20, "Both tanks were destroyed."); else { char str[80]; strcpy(str,name[2-winner]); strcat(str,"\'s tank was destroyed."); overwrite (CORNER+64, 20, str); } escape: overwrite (CORNER+64, 40, "Do you want to play again? "); do anoth = toupper(getchar_x()); while (anoth != 'Y' && anoth != 'N'); if (anoth == 'N') break; outtextxy (0, CORNER+280, 40, "Y"); overwrite (CORNER+64, 60, "Do you want a new field? "); do newf = toupper(getchar_x()); while (newf != 'Y' && newf != 'N'); if (newf=='Y') setupfield(); } XFreeGC (dis, gc); XDestroyWindow (dis, win); XCloseDisplay (dis); puts("**** FINAL SCORE ****"); printf("%12s: %d\n",name[0],score[0]); printf("%12s: %d\n",name[1],score[1]); printf("%12s: %d\n","ties",score[2]); }