/* lunar - Simple X11 Lunar Lander Copyright (C) 2005 Ian Cowburn (ianc@noddybox.demon.co.uk) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ------------------------------------------------------------------------- This code is rather messy as it is a quick tidy up from the K&R original. */ static char rcs_id[]="$Id$"; #include #include #include #include #include #include #include #include #include #include #include "Xbit.h" #define WINX 100 #define WINY 100 #define WINW 320 #define WINWH 160 #define WINH 200 #define WINHH 100 #define RND(x) (rand()%(x)) #define ITOF(x) ((x)<<8) #define FTOI(x) ((x)>>8) #define ABS(x) ((x)<0 ? (-(x)) : (x)) #define SGN(x) ((x) ? ((x)/ABS(x)) : (0)) #define DABS(x) ((x)<0.0 ? (-(x)) : (x)) #define AT(p,x,y) *((p)+(x)+(y)*WINW) #define HISCFILE "hisc.ll" #define LEVELDIR "LEVELS/" #define CH(x,y) ((x)*SCALE),((y)*SCALE),SCALE,SCALE #define CHSC(x,y,sx,sy) ((x)*SCALE),((y)*SCALE),SCALE*sx,SCALE*sy static XSizeHints size_hints; static ulong black,white; static Display *disp; static Window top,sub; static Colormap cm; static XFontStruct *font; static XImage *img; static uchar *coll_data; static double si[3600],co[3600]; static XFuncControl Key(Window w, XPressRelease s, XEvent *e); static XFuncControl ProcessTitle(void),ProcessIntro(void),ProcessGame(void), ProcessGameOver(void),ProcessHiScore(void); /* Hiscore */ #define NOHI 3 typedef struct { char name[4]; int score; int no; } HiSc; static HiSc hisc[NOHI]= { {"N.B",100,1}, {"N.B",100,1}, {"N.B",100,1}, }; /* Keycontrols */ static XWindowKeyCallback key[2]= { {0,Key}, {0,NULL} }; #define NONE -1 #define FINE_LEFT 0 #define FINE_RIGHT 1 #define THRUST 2 #define LEFT 3 #define RIGHT 4 #define QUIT 5 #define SCALE_UP 6 #define SCALE_DOWN 7 #define PAUSE 8 static int inkey=NONE; static KeySym last_keysym; static int keymap[PAUSE+1]= {False,False,False,False,False, False,False,False,False}; /* Collision data codes - NB : order is important for Vector collisions - a collision of '4' will override a collision with '3' */ #define CNONE 0 #define CPAD 1 #define CASTEROID 2 #define CMOUNTAIN 3 #define ISCMINE(c) ((c)&(0x80)) #define CMINE(x) ((x)+0x80) /* Colour vars */ #define NOCOLS 8 #define RNDCOL pix[(RND(NOCOLS-2)+2)] #define RGB(r,g,b) {(r)*255,(g)*255,(b)*255} static ulong pix[NOCOLS]; static Colour cols[NOCOLS]= { RGB(0,0,0), /* BLACK */ RGB(255,255,255), /* WHITE */ RGB(255,100,100), /* RED */ RGB(100,255,100), /* GREEN */ RGB(100,100,255), /* BLUE */ RGB(255,255,0), /* YELLOW */ RGB(0,255,255), /* CYAN */ RGB(165,42,42) /* BROWN */ }; #define IBLACK 0 #define IWHITE 1 #define IRED 2 #define IGREEN 3 #define IBLUE 4 #define IYELLOW 5 #define ICYAN 6 #define IBROWN 7 #define BLACK pix[IBLACK] #define WHITE pix[IWHITE] #define RED pix[IRED] #define GREEN pix[IGREEN] #define BLUE pix[IBLUE] #define YELLOW pix[IYELLOW] #define CYAN pix[ICYAN] #define BROWN pix[IBROWN] /* 2D Vector defs */ #define VECPT 32 #define VECLN 32 typedef struct { double x,y; } VecPt; typedef struct { int p1,p2; } VecLine; typedef struct { VecPt pos; int ang; int no_pt; VecPt pt[VECPT]; int no_ln; VecLine ln[VECLN]; VecPt work[VECPT]; ulong colour; int collcode; int coll[VECLN]; } VecObject; /* Lander vector object */ static VecObject base_lander= { {0.0,0.0}, /* pos */ 0, /* ang */ 7, /* no_pt */ { /* pt[] */ { 0.0, -10.0}, {-10.0, 10.0}, { 10.0, 10.0}, { -7.0, 10.0}, { 7.0, 10.0}, {-10.0, 20.0}, { 10.0, 20.0}, }, 5, /* no_ln */ { /* ln[] */ {0,1}, {1,2}, {2,0}, {3,5}, {4,6}, }, { /* work[] */ {0.0,0.0}, }, IWHITE, /* colour */ CNONE, /* collcode */ {0,}, /* coll */ }; static VecObject lander; /* Particle definitions */ #define NOPLIST 4 #define PSHORT 0 #define PMID 1 #define PLONG 2 #define PULTRA 3 typedef struct { double x,y; } ParticlePt; typedef struct part { int life; ParticlePt p; ParticlePt i; struct part *next; } Particle; static Particle *p_head[NOPLIST]; static Particle *p_tail[NOPLIST]; /* Now we are using MIT-SHM structures, there should be more than enough power for some background stars to give the player a visual cue of bearing */ #define NOBDSTAR 50 typedef struct { double x,y; } BdStar; static BdStar bdrop[NOBDSTAR]; /* Level data - should improve this really... Using up 3Mb statically for level definitions is probably a bit _too_ much! :-} */ #define MAXLEVEL 32 #define MAXLEVPOLY 64 #define MOUNTAIN 0 #define ASTEROIDCW 1 #define ASTEROIDACW 2 #define PAD 3 #define MINE 4 typedef struct { int no; char *name; int type[MAXLEVPOLY]; int draw[MAXLEVPOLY]; VecObject v[MAXLEVPOLY]; } LevelDef; static int no_levels; static char *level_set; static LevelDef levdata[MAXLEVEL]; static LevelDef level; /* Damage vars */ #define DAMAGE_NONE 0 #define DAMAGE_LEFT 1 #define DAMAGE_RIGHT 2 #define DAMAGE_MAIN 3 static int damage; /* Control vars */ static int FUEL =500; static int SCORE =0; static int FONT =1; static int LEVEL =0; static int SHOWVEC =0; static double GRAVITY =0.1; static double MAXGRAV =8.0; static double JET =0.5; static int SCALE =1; static void POKEIMG_S(XImage *img, int x, int y, ulong col); static void POKEIMG_NS(XImage *img, int x, int y, ulong col); static void (*POKEIMG)(XImage *img, int x, int y, ulong col); static int quit=False; static int do_intro; static int score,rot,fuel,landing,lev; static double shipxi,shipyi; static int offx,offy; /* Protos */ static void ClearKeys(void); static void DefineLevel(void); static void DrawObject(VecObject *o); static void ClearParticles(void); static void InitBdrop(void); static int Paused(int k); static void ReadScores(void); static void WriteScores(void); static void ReadLevels(void); int main(int argc,char *argv[]) { int f,r,x,y,arg,noshm; unsigned long evmask; arg=1; noshm=False; if (argc>arg) if (!strcmp(argv[arg],"-noshm")) { noshm=True; arg++; } if (argc>arg) { SCALE=atoi(argv[arg]); if (SCALE<1) { fprintf(stderr,"scale param must be +ve\n"); exit(1); } } if (SCALE==1) POKEIMG=POKEIMG_NS; else POKEIMG=POKEIMG_S; for(f=0;f<3600;f++) { si[f]=sin(M_PI/1800.0*f); co[f]=cos(M_PI/1800.0*f); } for(f=0;f0); if (score>-1) { ClearKeys(); XDoWindows(NULL,NULL,key,ProcessGameOver); ReadScores(); if (score>hisc[NOHI-1].score) { XDoWindows(NULL,NULL,key,ProcessHiScore); WriteScores(); } } ClearKeys(); XDoWindows(NULL,NULL,key,ProcessTitle); } XAutoRepeatOn(disp); DestroyXImage(img); XCloseDisplay(disp); } /* ----------------------------------------------- KEY AND UTILITY */ static XFuncControl Key(Window w, XPressRelease s, XEvent *e) { if (s==XRELEASE) { last_keysym=XK_VoidSymbol; switch(XLookupKeysym((XKeyEvent *)e,ShiftMapIndex)) { case XK_A: case XK_a: keymap[FINE_LEFT]=False; break; case XK_D: case XK_d: keymap[FINE_RIGHT]=False; break; case XK_Z: case XK_z: keymap[LEFT]=False; break; case XK_C: case XK_c: keymap[RIGHT]=False; break; case XK_period: keymap[THRUST]=False; break; case XK_P: case XK_p: keymap[PAUSE]=False; break; case XK_Q: case XK_q: keymap[QUIT]=False; break; case XK_bracketleft: keymap[SCALE_DOWN]=False; break; case XK_bracketright: keymap[SCALE_UP]=False; break; default: keymap[NONE]=False; break; } } if (s==XPRESS) { inkey=NONE; switch(last_keysym=XLookupKeysym((XKeyEvent *)e,ShiftMapIndex)) { case XK_A: case XK_a: inkey=FINE_LEFT; break; case XK_D: case XK_d: inkey=FINE_RIGHT; break; case XK_Z: case XK_z: inkey=LEFT; break; case XK_C: case XK_c: inkey=RIGHT; break; case XK_period: inkey=THRUST; break; case XK_P: case XK_p: inkey=PAUSE; break; case XK_Q: case XK_q: inkey=QUIT; break; case XK_bracketleft: inkey=SCALE_DOWN; break; case XK_bracketright: inkey=SCALE_UP; break; default: inkey=NONE; break; } if (inkey!=NONE) keymap[inkey]=True; } return XFUNCCONT; } static int GetKey(void) { int k=inkey; if ((inkey=SCALE_UP)||(inkey=SCALE_UP)||(inkey==PAUSE)|| (inkey==QUIT)||(inkey=THRUST)) inkey=NONE; return k; } static KeySym RawKey(void) { KeySym k=last_keysym; last_keysym=XK_VoidSymbol; return k; } static void ClearKeys(void) { int f; inkey=NONE; for(f=0;f<5;f++) keymap[f]=False; } /* ----------------------------------------------- GRAPHICS UTILS */ static void Cls(void) { ClsXImage(img); memset(coll_data,CNONE,WINW*WINH); } static void Update(void) { DrawXImage(img); XSync(disp,False); } static void Centre(int y, ulong c, const char *s) { XIprintf(img,CH(WINWH-strlen(s)*4,y),c,"%s",s); } static void POKEIMG_NS(XImage *img,int x,int y,ulong c) { XPutPixel(img,x,y,c); } static void POKEIMG_S(XImage *img,int x,int y,ulong c) { int sx,sy; for(sx=0;sx=0)&&(x=0)&&(y3599) level.v[f].ang-=3600; break; case ASTEROIDACW: if ((level.v[f].ang-=20)<0) level.v[f].ang+=3600; break; case PAD: break; case MINE: break; } DrawObject(&level.v[f]); } } /* ----------------------------------------------- VECTOR OBJECT */ static int vecscale=0; #define SetScale(s) vecscale=(s) static void LinePlot(int x,int y,ulong c,int col,int *retcoll) { if ((x<0)||(x>=WINW)||(y<0)||(y>=WINH)) return; POKEIMG(img,x,y,c); if (AT(coll_data,x,y)>*retcoll) *retcoll=AT(coll_data,x,y); if (col!=CNONE) AT(coll_data,x,y)=col; } static void Line(VecPt *p1,VecPt *p2, ulong c, int collcode, int *coll) { int f; int dx,dy,ix,iy,incrE,incrNE,d,x,y,ymode; int p1x,p1y,p2x,p2y; p1x=(int)p1->x; p1y=(int)p1->y; p2x=(int)p2->x; p2y=(int)p2->y; if ((p1x<0)&&(p2x<0)) return; if ((p1x>=WINW)&&(p2x>=WINW)) return; if ((p1y<0)&&(p2y<0)) return; if ((p1y>=WINH)&&(p2y>=WINH)) return; dx=p2x-p1x; dy=p2y-p1y; ix=SGN(dx); iy=SGN(dy); dx=ABS(dx); dy=ABS(dy); if (dy>dx) { ymode=True; d=dx*2-dy; incrE=dx*2; incrNE=(dx-dy)*2; } else { ymode=False; d=dy*2-dx; incrE=dy*2; incrNE=(dy-dx)*2; } x=p1x; y=p1y; LinePlot(x,y,c,collcode,coll); if (ymode) while(y!=p2y) { if (d<=0) { d+=incrE; y+=iy; } else { d+=incrNE; y+=iy; x+=ix; } LinePlot(x,y,c,collcode,coll); } else while(x!=p2x) { if (d<=0) { d+=incrE; x+=ix; } else { d+=incrNE; y+=iy; x+=ix; } LinePlot(x,y,c,collcode,coll); } } static void Rotate(VecPt *p1,VecPt *p2,int a) { double dx,dy,dco,dsi; dco=co[a]; dsi=si[a]; dx=p1->x; dy=p1->y; p2->x=(dco*dx+(-dsi)*dy); p2->y=(dsi*dx+dco*dy); } static void Scale(VecPt *p) { p->x/=vecscale; p->y/=vecscale; } static void DrawObject(VecObject *o) { int f; if (o->ang) for(f=0;fno_pt;f++) { Rotate((o->pt)+f,(o->work)+f,o->ang); o->work[f].x+=o->pos.x; o->work[f].y+=o->pos.y; if (vecscale) Scale(o->work+f); o->work[f].x+=WINWH; o->work[f].y+=WINHH; } else for(f=0;fno_pt;f++) { o->work[f].x=o->pt[f].x+o->pos.x; o->work[f].y=o->pt[f].y+o->pos.y; if (vecscale) Scale(o->work+f); o->work[f].x+=WINWH; o->work[f].y+=WINHH; } for(f=0;fno_ln;f++) { o->coll[f]=CNONE; Line(&o->work[o->ln[f].p1], &o->work[o->ln[f].p2], o->colour, o->collcode, &o->coll[f]); } } /* ----------------------------------------------- PARTICLE ROUTINES */ static void ClearParticles(void) { Particle *p; int f; for(f=0;fnext; free(p); } p_tail[f]=NULL; } } static void AddParticle(int ang,int rad,int list,int weight) { Particle *new; if (new=(Particle *)malloc(sizeof(Particle))) { new->next=NULL; switch(list) { case PSHORT: new->life=3; break; case PMID: new->life=7; break; case PLONG: new->life=20; break; case PULTRA: new->life=40; break; default: free(new); return; } new->p.x=-(double)rad*si[ang]; new->p.y=(double)rad*co[ang]; new->i.x=-si[ang]/weight; new->i.y=co[ang]/weight; if (p_head[list]) { p_tail[list]->next=new; p_tail[list]=new; } else p_head[list]=p_tail[list]=new; } } static void DrawParticles(void) { Particle *p; int f; for(f=0;flife)) { if (!(p_head[f]=p->next)) p_tail[f]=NULL; free(p); } p=p_head[f]; while(p) { p->life--; p->p.x+=p->i.x+shipxi; p->p.y+=p->i.y+shipyi; if (vecscale) Plot(WINWH+(int)p->p.x/vecscale, WINHH+(int)p->p.y/vecscale,RNDCOL); else Plot(WINWH+(int)p->p.x,WINHH+(int)p->p.y,RNDCOL); p=p->next; } } } static int NoParticles(void) { int f,ret; ret=True; for(f=0;f=(double)WINW) bdrop[f].x-=(double)WINW; if (bdrop[f].y<0.0) bdrop[f].y+=(double)WINH; if (bdrop[f].y>=(double)WINH) bdrop[f].y-=(double)WINH; Plot((int)bdrop[f].x,(int)bdrop[f].y,YELLOW); } } /* ----------------------------------------------- TITLE SCREEN */ static void DoDebugMenu(void) { static int debug_menu=False; KeySym k; int f; if ((k=RawKey())=='r') debug_menu=!debug_menu; if (debug_menu) { Cls (); for(f=0;f<128;f++) XIprintf(img,CH((f%40)*8,(f/40)*8),CYAN,"%c",f); XIprintf(img,CH(0,150),CYAN, "(y/u) LEVEL %2d : %s",LEVEL,levdata[LEVEL].name); XIprintf(img,CH(0,160),CYAN, "(s) SHOW VECTOR : %5d",SHOWVEC); XIprintf(img,CH(0,170),CYAN, "(f) FONT : %5d",FONT); XIprintf(img,CH(0,180),CYAN, "(h) SCORE : %5d",SCORE); XIprintf(img,CH(0,190),CYAN, "(l) FUEL : %5d",FUEL); XIprintf(img,CH(0,100),CYAN,"(x) SET TO DEFAULTS"); XIprintf(img,CH(0,108),CYAN,"(o) SET TO DEBUG"); XIprintf(img,CH(0,116),CYAN,"(w) SET TO SMALLER DEBUG"); switch(k) { case 'u': if (--LEVEL==-1) LEVEL=no_levels-1; break; case 'y': if (++LEVEL==no_levels) LEVEL=0; break; case 'f': FONT^=1; XISetFont(FONT); break; case 'h': SCORE=hisc[0].score; break; case 'l': if ((FUEL+=500)>10000) FUEL=0; break; case 's': SHOWVEC=!SHOWVEC; break; case 'x': SCORE=0; FUEL=500; break; case 'o': FUEL=10000; SCORE=hisc[0].score; break; case 'w': FUEL=100; SCORE=hisc[0].score; break; } } } static XFuncControl ProcessTitle(void) { static unsigned int ctr=0; char s[80]; int f,k; ctr++; Cls(); for(f=0;f<10;f++) Centre(15+f,((ctr+f)%(NOCOLS-2))+2,"LUNAR LANDER"); Centre(25,WHITE,"LUNAR LANDER"); Centre(45,RED,level_set); Centre(46,YELLOW,level_set); Centre(47,WHITE,level_set); if ((ctr/100)%2) { Centre(70,RNDCOL,"TOP PILOTS"); for(f=0;f-2.0)) { *land=True; landing++; score+=fuel; fuel+=200; lev=(lev+1)%no_levels; do_intro=True; for(r=0;r<500;r++) { AddParticle(2700-RND(500),RND(10)+10,PULTRA,1); AddParticle(900+RND(500),RND(10)+10,PULTRA,1); } shipxi=0.0; shipyi=0.0; return; } else { for(r=0;r3599) lander.ang-=3600; for(f=0;f<5;f++) { na=(lander.ang+800+RND(200))%3600; AddParticle(na,8+RND(3),PSHORT,10); } } } else if ((keymap[LEFT])&&(damage3599) lander.ang-=3600; for(f=0;f<5;f++) { na=(lander.ang+800+RND(200))%3600; AddParticle(na,8+RND(3),PMID,10); } } } if ((keymap[THRUST])&&(fuel>0)&&(damageMAXGRAV) shipxi=MAXGRAV; if (shipyi<-MAXGRAV) shipyi=-MAXGRAV; if (shipyi>MAXGRAV) shipyi=MAXGRAV; } DrawBdrop(); DrawParticles(); DrawLevel(); if (!dead) { DrawObject(&lander); if ((ctr/10)%2) { if (damage==DAMAGE_NONE) dmg=NULL; else if (damage==DAMAGE_LEFT) dmg="LEFT THRUSTER DAMAGED"; else if (damage==DAMAGE_RIGHT) dmg="BOTH SIDE THRUSTERS DAMAGED"; else dmg="ALL THRUSTERS DAMAGED!"; if(dmg) Centre(48,WHITE,dmg); } } XIprintf(img,CH(0,0),WHITE,"Fuel : %4d",fuel); if (SHOWVEC) XIprintf(img,CH(0,WINH-8),WHITE,"xi: %2.3f yi: %2.3f ang: %4d", shipxi,shipyi,lander.ang); /* Check collisions */ if ((!landed)&&(!dead)) CheckCollisions(&landed,&dead); Update(); if (k==SCALE_UP) { if (--scale<2) scale=0; InitBdrop(); SetScale(scale); } if (k==SCALE_DOWN) { if (scale) scale++; else scale=2; InitBdrop(); SetScale(scale); } if (k==QUIT) { score=-1; fuel=0; first=True; return XFUNCSTOP; } if ((dead)&&(NoParticles())) { first=True; return XFUNCSTOP; } if ((landed)&&(NoParticles())) { first=True; return XFUNCSTOP; } return XFUNCCONT; } /* ----------------------------------------------- PAUSE CODE */ static void DoPause(void) { static unsigned int c=0; c++; if ((c/10)%2) Centre(WINH/4,WHITE,"PAUSED"); else Centre(WINH/4,YELLOW,"PAUSED"); Update(); } static int Paused(int k) { static int paused=False; if (k==PAUSE) paused=!paused; if (paused) DoPause(); return paused; } /* ----------------------------------------------- GAME OVER */ static XFuncControl ProcessGameOver(void) { static int ctr=0; Cls (); ctr++; if ((ctr/10)%2) Centre(120,WHITE,"GAME OVER"); Update(); if (GetKey()==NONE) return XFUNCCONT; else return XFUNCSTOP; } /* ----------------------------------------------- HI SCORES */ static void ReadScores(void) { int fd,f; if ((fd=open(HISCFILE,O_RDONLY))==-1) return; for(f=0;f-1;f--) if (hisc[f].scorepos;f--) hisc[f]=hisc[f-1]; strcpy(hisc[pos].name,""); hisc[pos].score=score; hisc[pos].no=landing; first=False; } Cls(); Centre(16,WHITE,"CONGRATULATIONS!"); Centre(30,YELLOW,"ENTER YOUR INITIALS"); Centre(40,YELLOW,"FOR THE TOP PILOTS"); hisc[pos].name[len]=*let; hisc[pos].name[len+1]=0; for(f=0;fMAXLEVEL)) err("illegal no of levels"); for(f=0;f