JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
71f014bed52edcfe3f5758f159e1f4ada1c40487
[vor.git] / main.c
1 /* Variations on RockDodger
2  * Space Rocks copyright (C) 2001 Paul Holt <pad@pcholt.com>
3  *
4  * Project fork 2004, Jason Woofenden and Joshua Grams.
5  * (a whole bunch of modifications and project rename)
6
7  * This program is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU General Public License as published by the
9  * Free Software Foundation; either version 2 of the License, or (at your
10  * option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20  */
21
22 #undef DEBUG
23
24 extern int font_height;
25 void clearBuffer();
26
27 // includes {{{
28 #include "config.h"
29 #include <SDL/SDL.h>
30 #include <SDL/SDL_image.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <math.h>
35 #include <stdarg.h>
36
37 #include <sys/types.h>
38 #include <sys/stat.h>
39 #include <unistd.h>
40
41 #include "SFont.h"
42 // }}}
43 // constants {{{
44 // }}}
45 // macros {{{
46 #define CONDERROR(a) if((a)) {initerror = strdup(SDL_GetError());return 1;}
47 #define NULLERROR(a) CONDERROR((a) == NULL)
48 // }}}
49
50 // ************************************* STRUCTS
51 struct rock_struct {
52         // Array of black pixel coordinates. This is scanned 
53         // every frame to see if it's still black, and as
54         // soon as it isn't we BLOW UP
55         float x,y,xvel,yvel;
56         int active;
57         SDL_Surface *image;
58         int type_number;
59         float heat;
60 }; 
61 struct black_point_struct {
62         int x,y;
63 };
64 struct bangdots {
65         // Bang dots have the same colour as shield dots.
66         // Bang dots get darker as they age.
67         // Some are coloured the same as the ex-ship.
68         float x,y,dx,dy;
69         Uint16 c; // when zero, use heatcolor[bangdotlife]
70         float life;     // When reduced to 0, set active = 0
71         int active;
72         float decay;// Amount by which to reduce life each time dot is drawn
73 };
74 struct enginedots {
75         // Engine dots stream out the back of the ship, getting darker as they go.
76         int active;
77         float x,y,dx,dy;
78         // The life of an engine dot 
79         // is a number starting at between 0 and 50 and counting backward.
80         float life;     // When reduced to 0, set active = 0
81 };
82 struct spacedot {
83         // Space dots are harmless background items
84         // All are active. When one falls off the edge, another is created at the start.
85         float x,y,dx;
86         Uint16 color;
87 };
88 // High score table {{{
89 struct highscore {
90         int score;
91         char *name;
92         int allocated;
93 } high[] = {
94         {13000,"Pad",0},
95         {12500,"Pad",0},
96         {6500,"Pad",0},
97         {5000,"Pad",0},
98         {3000,"Pad",0},
99         {2500,"Pad",0},
100         {2000,"Pad",0},
101         {1500,"Pad",0}
102 };
103 // }}}
104
105 // ************************************* VARS
106 // SDL_Surface global variables {{{
107 SDL_Surface 
108         *surf_screen,   // Screen
109         *surf_b_variations, // "variations" banner
110         *surf_b_on, // "on" banner
111         *surf_b_rockdodger, // "rockdodger" banner
112         *surf_b_game,   // Title element "game"
113         *surf_b_over,   // Title element "over"
114         *surf_ship,             // Spaceship element
115         *surf_life,     // Indicator of number of ships remaining
116         *surf_rock[NROCKS],     // THE ROCKS
117         *surf_deadrock[NROCKS], // THE DEAD ROCKS
118         *surf_font_big; // The big font
119 // }}}
120 // Structure global variables {{{
121 struct enginedots edot[MAXENGINEDOTS], *dotptr = edot;
122 struct rock_struct rock[MAXROCKS], *rockptr = rock;
123 struct black_point_struct black_point[MAXBLACKPOINTS], *blackptr = black_point;
124 struct bangdots bdot[MAXBANGDOTS], *bdotptr = bdot;
125 struct spacedot sdot[MAXSPACEDOTS];
126 // }}}
127 // Other global variables {{{
128 char topline[1024];
129 char *initerror = "";
130 char name[1024], debug1[1024];
131
132 float xship,yship = 240.0;      // X position, 0..XSIZE
133 float xvel,yvel;        // Change in X position per tick.
134 float rockrate,rockspeed;
135 float movementrate;
136 float yscroll;
137 float scrollvel;
138
139 int nships,score,initticks,ticks_since_last, last_ticks;
140 int gameover;
141 int countdown = 0;
142 int maneuver = 0;
143 int oss_sound_flag = 0;
144 int tail_plume = 0; // display big engine at the back?
145 int friction = 0;       // should there be friction?
146 int scorerank;
147 float fadetimer = 0,faderate;
148
149 int pausedown = 0,paused = 0;
150
151 // bangdot start (bd1) and end (bd2) position:
152 int bd1 = 0, bd2 = 0;
153
154 int xoffset[NROCKS][MAXROCKHEIGHT];
155
156 enum states {
157         TITLE_PAGE,
158         GAMEPLAY,
159         DEAD_PAUSE,
160         GAME_OVER,
161         HIGH_SCORE_ENTRY,
162         HIGH_SCORE_DISPLAY,
163         DEMO
164 };
165 enum states state = TITLE_PAGE;
166 float state_timeout = 600.0;
167
168 const int fakesin[] = {0,1,0,-1};
169 const int fakecos[] = {1,0,-1,0};
170 #define NSEQUENCE 2
171 char *sequence[] = {
172         "Press SPACE to start",
173         "http://qualdan.com/vor/"
174 };
175
176 int bangdotlife, nbangdots;
177 Uint16 heatcolor[W*3];
178
179 char *data_dir;
180 extern char *optarg;
181 extern int optind, opterr, optopt;
182 // }}}
183
184 float dist_sq(float x1, float y1, float x2, float y2)
185 {
186         return (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1);
187 }
188
189 // ************************************* FUNCS
190
191 FILE *hs_fopen(char *mode) {
192         FILE *f;
193         mode_t mask;
194         mask = umask(0111);
195         if(f = fopen("/usr/share/vor/.highscore",mode)) {
196                 umask(mask);
197                 return f;
198         }
199         else {
200                 char s[1024];
201                 umask(0177);
202                 sprintf(s,"%s/.vor-high",getenv("HOME"));
203                 if(f = fopen(s,mode)) {
204                         umask(mask);
205                         return f;
206                 }
207                 else {
208                         umask(mask);
209                         return 0;
210                 }
211         }
212 }
213 void read_high_score_table() {
214         FILE *f;
215         int i;
216         if(f = hs_fopen("r")) {
217                 // If the file exists, read from it
218                 for(i = 0; i<8; i++) {
219                         char s[1024];
220                         int highscore;
221                         if(fscanf (f, "%d %[^\n]", &highscore, s) != 2) {
222                                 break;
223                         }
224                         if(high[i].allocated) {
225                                 free(high[i].name);
226                         }
227                         high[i].name = strdup(s);
228                         high[i].score = highscore;
229                         high[i].allocated = 1;
230                 }
231                 fclose(f);
232         }
233 }
234 void write_high_score_table() {
235         FILE *f;
236         int i;
237         if(f = hs_fopen("w")) {
238                 // If the file exists, write to it
239                 for(i = 0; i<8; i++) {
240                         fprintf (f, "%d %s\n", high[i].score, high[i].name);
241                 }
242                 fclose(f);
243         }
244 }
245 void snprintscore(char *s, size_t n, int score) {
246         int min = score/60000;
247         int sec = score/1000%60;
248         int tenths = score%1000/100;
249         if(min) {
250                 snprintf(s, n, "%2d:%.2d.%d", min, sec, tenths);
251         } else {
252                 snprintf(s, n, " %2d.%d", sec, tenths);
253         }
254 }
255 float rnd() {
256         return (float)random()/(float)RAND_MAX;
257 }
258 void init_engine_dots() {
259         int i;
260         for(i = 0; i<MAXENGINEDOTS; i++) {
261                 edot[i].active = 0;
262         }
263 }
264 void init_space_dots() {
265         int i,intensity;
266         for(i = 0; i<MAXSPACEDOTS; i++) {
267                 float r;
268
269                 sdot[i].x = rnd()*(XSIZE-5);
270                 sdot[i].y = rnd()*(YSIZE-5);
271
272                 r = rnd()*rnd();
273
274                 sdot[i].dx = -r*4;
275                 // -1/((1-r) + .3);
276                 intensity = (int)(r*180 + 70);
277                 sdot[i].color = SDL_MapRGB(surf_screen->format,intensity,intensity,intensity);
278
279         }
280 }
281
282 int makebangdots(int xbang, int ybang, int xvel, int yvel, SDL_Surface *s, int power) {
283
284         // TODO - stop generating dots after a certain amount of time has passed, to cope with slower CPUs.
285         // TODO - generate and display dots in a circular buffer
286
287         int i,x,y,n,endcount;
288         Uint16 *rawpixel,c;
289         double theta,r,dx,dy;
290         int begin_generate;
291
292         begin_generate = SDL_GetTicks();
293
294         SDL_LockSurface(s);
295         rawpixel = (Uint16 *) s->pixels;
296
297         //for(n = 0; n <= power/2; n++) {
298
299         endcount = 0;
300         while (endcount<3) {
301
302         for(x = 0; x<s->w; x++) {
303                 for(y = 0; y<s->h; y++) {
304                         c = rawpixel[s->pitch/2*y + x];
305                         if(c && c != SDL_MapRGB(s->format,0,255,0)) {
306
307                                 theta = rnd()*M_PI*2;
308
309                                 r = 1-(rnd()*rnd());
310
311                                 bdot[bd2].dx = (power/50.0)*45.0*cos(theta)*r + xvel;
312                                 bdot[bd2].dy = (power/50.0)*45.0*sin(theta)*r + yvel;
313                                 bdot[bd2].x = x + xbang;
314                                 bdot[bd2].y = y + ybang;
315
316                                 // Replace the last few bang dots with the pixels from the exploding object
317                                 bdot[bd2].c = (endcount>0)?c:0;
318                                 bdot[bd2].life = 100;
319                                 bdot[bd2].decay = rnd()*3 + 1;
320                                 bdot[bd2].active = 1;
321
322                                 bd2++;
323                                 bd2 %= MAXBANGDOTS;
324
325                                 // If the circular buffer is filled, who cares? They've had their chance.
326                                 //if(bd2 == bd1-1) goto exitloop;
327
328                         }
329                 }
330         }
331
332         if(SDL_GetTicks() - begin_generate > 7) endcount++;
333
334         }
335 exitloop:
336
337         SDL_UnlockSurface(s);
338
339 }
340
341 void draw_bang_dots(SDL_Surface *s) {
342         int i;
343         int first_i, last_i;
344         Uint16 *rawpixel;
345         rawpixel = (Uint16 *) s->pixels;
346
347         first_i = -1;
348
349         for(i = bd1; (bd1 <= bd2)?(i<bd2):(i >= bd1 && i < bd2); last_i = ++i) {
350
351                 i %= MAXBANGDOTS;
352
353                 if(bdot[i].x <= 0 || bdot[i].x >= XSIZE || bdot[i].y <= 0 || bdot[i].y >= YSIZE) {
354                         // If the dot has drifted outside the perimeter, kill it
355                         bdot[i].active = 0;
356                 }
357
358                 if(bdot[i].active) {
359                         if(first_i < 0)
360                         first_i = i;
361                         //last_i = i + 1;
362                         rawpixel[(int)(s->pitch/2*(int)(bdot[i].y)) + (int)(bdot[i].x)] = bdot[i].c ? bdot[i].c : heatcolor[(int)(bdot[i].life*3)];
363                         bdot[i].life -= bdot[i].decay;
364                         bdot[i].x += bdot[i].dx*movementrate;
365                         bdot[i].y += bdot[i].dy*movementrate + yscroll;
366
367                         if(bdot[i].life<0)
368                         bdot[i].active = 0;
369                 }
370         }
371
372         if(first_i >= 0) {
373                 bd1 = first_i;
374                 bd2 = last_i;
375         }
376         else {
377                 bd1 = 0;
378                 bd2 = 0;
379         }
380
381 }
382
383
384 void draw_space_dots(SDL_Surface *s) {
385         int i;
386         Uint16 *rawpixel;
387         rawpixel = (Uint16 *) s->pixels;
388
389         for(i = 0; i<MAXSPACEDOTS; i++) {
390                 if(sdot[i].y<0) {
391                         sdot[i].y = 0;
392                 }
393                 rawpixel[(int)(s->pitch/2*(int)sdot[i].y) + (int)(sdot[i].x)] = sdot[i].color;
394                 sdot[i].x += sdot[i].dx*movementrate;
395                 sdot[i].y += yscroll;
396                 if(sdot[i].y > YSIZE) {
397                         sdot[i].y -= YSIZE;
398                 } else if(sdot[i].y < 0) {
399                         sdot[i].y += YSIZE;
400                 }
401                 if(sdot[i].x<0) {
402                         sdot[i].x = XSIZE;
403                 }
404         }
405 }
406
407 void draw_engine_dots(SDL_Surface *s) {
408         int i;
409         Uint16 *rawpixel;
410         rawpixel = (Uint16 *) s->pixels;
411
412         for(i = 0; i<MAXENGINEDOTS; i++) {
413                 if(edot[i].active) {
414                         edot[i].x += edot[i].dx*movementrate;
415                         edot[i].y += edot[i].dy*movementrate + yscroll;
416                         if((edot[i].life -= movementrate*3)<0 || edot[i].y<0 || edot[i].y>YSIZE) {
417                                 edot[i].active = 0;
418                         } else if(edot[i].x<0 || edot[i].x>XSIZE) {
419                                 edot[i].active = 0;
420                         } else {
421                                 int heatindex;
422                                 heatindex = edot[i].life * 6;
423                                 //rawpixel[(int)(s->pitch/2*(int)(edot[i].y)) + (int)(edot[i].x)] = lifecolor[(int)(edot[i].life)];
424                                 rawpixel[(int)(s->pitch/2*(int)(edot[i].y)) + (int)(edot[i].x)] = heatindex>3*W ? heatcolor[3*W-1] : heatcolor[heatindex];
425                         }
426                 }
427         }
428 }
429
430 void create_engine_dots(int newdots) {
431         int i;
432         double theta,r,dx,dy;
433
434         if(!tail_plume) return;
435
436         if(state == GAMEPLAY) {
437                 for(i = 0; i<newdots*movementrate; i++) {
438                         if(dotptr->active == 0) {
439                                 theta = rnd()*M_PI*2;
440                                 r = rnd();
441                                 dx = cos(theta)*r;
442                                 dy = sin(theta)*r;
443
444                                 dotptr->active = 1;
445                                 dotptr->x = xship + surf_ship->w/2-14;
446                                 dotptr->y = yship + surf_ship->h/2 + (rnd()-0.5)*5-1;
447                                 dotptr->dx = 10*(dx-1.5) + xvel;
448                                 dotptr->dy = 1*dy + yvel;
449                                 dotptr->life = 45 + rnd(1)*5;
450
451                                 dotptr++;
452                                 if(dotptr-edot >= MAXENGINEDOTS) {
453                                         dotptr = edot;
454                                 }
455                         }
456                 }
457         }
458 }
459
460 void create_engine_dots2(int newdots, int m) {
461         int i;
462         double theta, theta2, dx, dy, adx, ady;
463
464         // Don't create fresh engine dots when
465         // the game is not being played and a demo is not beng shown
466         if(state != GAMEPLAY && state != DEMO) return;
467
468         for(i = 0; i<newdots; i++) {
469                 if(dotptr->active == 0) {
470                         theta = rnd()*M_PI*2;
471                         theta2 = rnd()*M_PI*2;
472
473                         dx = cos(theta) * fabs(cos(theta2));
474                         dy = sin(theta) * fabs(cos(theta2));
475                         adx = fabs(dx);
476                         ady = fabs(dy);
477
478
479                         dotptr->active = 1;
480                         dotptr->x = xship + surf_ship->w/2 + (rnd()-0.5)*3;
481                         dotptr->y = yship + surf_ship->h/2 + (rnd()-0.5)*3;
482
483                         switch(m) {
484                                 case 0:
485                                         dotptr->x -= 14;
486                                         dotptr->dx = -20*adx + xvel;
487                                         dotptr->dy = 2*dy + yvel;
488                                         dotptr->life = 60 * adx;
489                                 break;
490                                 case 1:
491                                         dotptr->dx = 2*dx + xvel;
492                                         dotptr->dy = -20*ady + yvel;
493                                         dotptr->life = 60 * ady;
494                                 break;
495                                 case 2:
496                                         dotptr->x += 14;
497                                         dotptr->dx = 20*adx + xvel;
498                                         dotptr->dy = 2*dy + yvel;
499                                         dotptr->life = 60 * adx;
500                                 break;
501                                 case 3:
502                                         dotptr->dx = 2*dx + xvel;
503                                         dotptr->dy = 20*ady + yvel;
504                                         dotptr->life = 60 * ady;
505                                 break;
506                         }
507                         dotptr++;
508                         if(dotptr-edot >= MAXENGINEDOTS) {
509                                 dotptr = edot;
510                         }
511                 }
512         }
513 }
514
515 int drawdots(SDL_Surface *s) {
516         int m, scorepos, n;
517
518         SDL_LockSurface(s);
519         // Draw the background stars aka space dots
520         draw_space_dots(s);
521
522         // Draw the score when playing the game or whn the game is freshly over
523         if(1 || state == GAMEPLAY || state == DEAD_PAUSE || state == GAME_OVER ) {
524                 SDL_UnlockSurface(s);
525
526                 scorepos = XSIZE-250;
527                 n = snprintf(topline, 50, "Time: ");
528                 snprintscore(topline + n, 50-n, score);
529                 PutString(s,scorepos,0,topline);
530
531                 SDL_LockSurface(s);
532         }
533
534         // Draw all the engine dots
535         draw_engine_dots(s);
536
537         // Create more engine dots comin out da back
538         if(!gameover)
539         create_engine_dots(200);
540
541         // Create engine dots out the side we're moving from
542         for(m = 0; m<4; m++) {
543                 if(maneuver & 1<<m) { // 'maneuver' is a bit field
544                         create_engine_dots2(80,m);
545                 }
546         }
547
548         // Draw all outstanding bang dots
549         //if(bangdotlife-- > 0) 
550         draw_bang_dots(s);
551
552         SDL_UnlockSurface(s);
553 }
554
555 char * load_file(char *s) {
556         static char retval[1024];
557         snprintf(retval, 1024, "%s/%s", data_dir, s);
558         return retval;
559 }
560
561
562 int missing(char *dirname) {
563         struct stat buf;
564         stat(dirname, &buf);
565         return (!S_ISDIR(buf.st_mode));
566 }
567
568 int init(int fullscreen) {
569
570         int i,j;
571         SDL_Surface *temp;
572         Uint16 *raw_pixels;
573         Uint32 flag;
574
575         read_high_score_table();
576
577         // Where are our data files?
578         // default: ./data
579         // second alternative: RD_DATADIR
580         // final alternative: /usr/share/vor
581         data_dir = strdup("./data");
582         if(missing(data_dir)) {
583                 char *env;
584                 env = getenv("RD_DATADIR");
585                 if(env != NULL) {
586                         data_dir = strdup(env);
587                         if(missing(data_dir)) {
588                                 fprintf (stderr,"Cannot find data directory $RD_DATADIR\n");
589                                 exit(-1);
590                         }
591                 } else {
592                         data_dir = strdup("/usr/share/vor");
593                         if(missing(data_dir)) {
594                                 fprintf (stderr,"Cannot find data in %s\n", data_dir);
595                                 exit(-2);
596                         }
597                 }
598         }
599
600         if(oss_sound_flag) {
601
602         // Initialise SDL with audio and video
603         if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) != 0) {
604                 oss_sound_flag = 0;
605                 printf ("Can't open sound, starting without it\n");
606                 atexit(SDL_Quit);
607         } else {
608                 atexit(SDL_Quit);
609                 atexit(SDL_CloseAudio);
610                 oss_sound_flag = init_sound();
611         }
612
613         } else {
614                 // Initialise with video only
615                 CONDERROR(SDL_Init(SDL_INIT_VIDEO) != 0);
616                 atexit(SDL_Quit);
617         }
618
619         if(oss_sound_flag)
620         play_tune(0);
621
622         // Attempt to get the required video size
623         flag = SDL_DOUBLEBUF | SDL_HWSURFACE;
624         if(fullscreen) flag |= SDL_FULLSCREEN;
625         surf_screen = SDL_SetVideoMode(XSIZE,YSIZE,16,flag);
626
627         // Set the title bar text
628         SDL_WM_SetCaption("Rock Dodgers", "rockdodgers");
629
630         NULLERROR(surf_screen);
631
632         // Set the heat color from the range 0 (cold) to 300 (blue-white)
633         for(i = 0; i<W*3; i++) {
634                 heatcolor[i] = SDL_MapRGB(
635                         surf_screen->format,
636                         (i<W)?(i*M/W):(M),(i<W)?0:(i<2*W)?((i-W)*M/W):M,(i<2*W)?0:((i-W)*M/W) // Got that?
637                 );
638         }
639
640         // Load the banners
641         NULLERROR(temp = IMG_Load(load_file("banners/variations.png")));
642         NULLERROR(surf_b_variations = SDL_DisplayFormat(temp));
643
644         NULLERROR(temp = IMG_Load(load_file("banners/on.png")));
645         NULLERROR(surf_b_on = SDL_DisplayFormat(temp));
646
647         NULLERROR(temp = IMG_Load(load_file("banners/rockdodger.png")));
648         NULLERROR(surf_b_rockdodger = SDL_DisplayFormat(temp));
649
650         NULLERROR(temp = IMG_Load(load_file("banners/game.png")));
651         NULLERROR(surf_b_game = SDL_DisplayFormat(temp));
652
653         NULLERROR(temp = IMG_Load(load_file("banners/over.png")));
654         NULLERROR(surf_b_over = SDL_DisplayFormat(temp));
655
656         surf_font_big = IMG_Load(load_file(BIG_FONT_FILE));
657         InitFont(surf_font_big);
658
659         // Load the spaceship graphic.
660         NULLERROR(temp = IMG_Load(load_file("sprites/ship.png")));
661         NULLERROR(surf_ship = SDL_DisplayFormat(temp));
662
663         // Load the life indicator (small ship) graphic.
664         NULLERROR(temp = IMG_Load(load_file("indicators/life.png")));
665         NULLERROR(surf_life = SDL_DisplayFormat(temp));
666
667         // Create the array of black points;
668         SDL_LockSurface(surf_ship);
669         raw_pixels = (Uint16 *) surf_ship->pixels;
670         for(i = 0; i<surf_ship->w; i++) {
671                 for(j = 0; j<surf_ship->h; j++) {
672                         if(raw_pixels[j*(surf_ship->pitch)/2 + i] == 0) {
673                                 blackptr->x = i;
674                                 blackptr->y = j;
675                                 blackptr++;
676                         }
677                 }
678         }
679
680         SDL_UnlockSurface(surf_ship);
681
682         init_engine_dots();
683         init_space_dots();
684
685         // Load all our lovely rocks
686         for(i = 0; i<NROCKS; i++) {
687                 char a[100];
688
689                 sprintf(a,load_file("sprites/rock%d.png"),i);
690                 NULLERROR(temp = IMG_Load(a));
691                 NULLERROR(surf_rock[i] = SDL_DisplayFormat(temp));
692
693                 sprintf(a,load_file("sprites/deadrock%d.png"),i);
694                 NULLERROR(temp = IMG_Load(a));
695                 NULLERROR(surf_deadrock[i] = SDL_DisplayFormat(temp));
696         }
697
698         // Remove the mouse cursor
699 #ifdef SDL_DISABLE
700         SDL_ShowCursor(SDL_DISABLE);
701 #endif
702
703         return 0;
704 }
705 int draw() {
706         int i,n;
707         SDL_Rect src,dest;
708         struct black_point_struct *p;
709         Uint16 *raw_pixels;
710         int bang, offset, x;
711         char *text;
712         float fadegame,fadeover;
713
714         char *statedisplay, buf[1024];
715         
716         bang = 0;
717
718         src.x = 0;
719         src.y = 0;
720         dest.x = 0;
721         dest.y = 0;
722
723         // Draw a fully black background
724         SDL_FillRect(surf_screen,NULL,0);
725
726
727 #ifdef DEBUG
728         // DEBUG {{{
729         // Show the current state
730         switch (state) {
731                 case TITLE_PAGE:
732                         statedisplay = "title_page";
733                 break;
734                 case GAMEPLAY:
735                         statedisplay = "gameplay";
736                 break;
737                 case DEAD_PAUSE:
738                         statedisplay = "dead_pause";
739                 break;
740                 case GAME_OVER:
741                         statedisplay = "game_over";
742                 break;
743                 case HIGH_SCORE_ENTRY:
744                         statedisplay = "high_score_entry";
745                 break;
746                 case HIGH_SCORE_DISPLAY:
747                         statedisplay = "high_score_display";
748                 break;
749                 case DEMO:
750                         statedisplay = "demo";
751                 break;
752         }
753         snprintf(buf,1024, "mode = %s", statedisplay);
754         PutString(surf_screen,0,YSIZE-50,buf);
755         // }}}
756 #endif
757         
758         // Draw the background dots
759         drawdots(surf_screen);
760
761         // Draw ship
762         if(!gameover && (state == GAMEPLAY || state == DEMO) ) {
763                 src.w = surf_ship->w;
764                 src.h = surf_ship->h;
765                 dest.w = src.w;
766                 dest.h = src.h;
767                 dest.x = (int)xship;
768                 dest.y = (int)yship;
769                 SDL_BlitSurface(surf_ship,&src,surf_screen,&dest);
770         }
771
772         // Draw all the rocks, in all states
773         for(i = 0; i<MAXROCKS; i++) {
774                 if(rock[i].active) {
775
776                         src.w = rock[i].image->w;
777                         src.h = rock[i].image->h;
778                         dest.w = src.w;
779                         dest.h = src.h;
780                         dest.x = (int) rock[i].x;
781                         dest.y = (int) rock[i].y;
782
783                         // Draw the rock
784                         SDL_BlitSurface(rock[i].image,&src,surf_screen,&dest);
785
786                         // Draw the heated part of the rock, in an alpha which reflects the
787                         // amount of heat in the rock.
788                         if(rock[i].heat>0) {
789                                 SDL_Surface *deadrock;
790                                 deadrock = surf_deadrock[rock[i].type_number];
791                                 SDL_SetAlpha(deadrock,SDL_SRCALPHA,rock[i].heat*255/rock[i].image->h);
792                                 dest.x = (int) rock[i].x; // kludge
793                                 SDL_BlitSurface(deadrock,&src,surf_screen,&dest);
794                                 if(rnd()<0.3) {
795                                         rock[i].heat -= movementrate;
796                                 }
797                         }
798
799                         // If the rock is heated past a certain point, the water content of
800                         // the rock flashes to steam, releasing enough energy to destroy
801                         // the rock in spectacular fashion.
802                         if(rock[i].heat>rock[i].image->h) {
803                                 rock[i].active = 0;
804                                 play_sound(1 + (int)(rnd()*3));
805                                 makebangdots(rock[i].x,rock[i].y,rock[i].xvel,rock[i].yvel,rock[i].image,10);
806                         }
807
808                 }
809         }
810
811         // If it's game over, show the game over graphic in the dead centre
812         switch (state) {
813                 case GAME_OVER:
814                         if(fadetimer<3.0/faderate) {
815                                 fadegame = fadetimer/(3.0/faderate);
816                         } else {
817                                 fadegame = 1.0;
818                         }
819
820                         if(fadetimer<3.0/faderate) {
821                                 fadeover = 0.0;
822                         } else if(fadetimer<6.0/faderate) {
823                                 fadeover = ((3.0/faderate)-fadetimer)/(6.0/faderate);
824                         } else {
825                                 fadeover = 1.0;
826                         }
827
828                         src.w = surf_b_game->w;
829                         src.h = surf_b_game->h;
830                         dest.w = src.w;
831                         dest.h = src.h;
832                         dest.x = (XSIZE-src.w)/2;
833                         dest.y = (YSIZE-src.h)/2-40;
834                         SDL_SetAlpha(surf_b_game, SDL_SRCALPHA, (int)(fadegame*(200 + 55*cos(fadetimer += movementrate/1.0))));
835                         SDL_BlitSurface(surf_b_game,&src,surf_screen,&dest);
836
837                         src.w = surf_b_over->w;
838                         src.h = surf_b_over->h;
839                         dest.w = src.w;
840                         dest.h = src.h;
841                         dest.x = (XSIZE-src.w)/2;
842                         dest.y = (YSIZE-src.h)/2 + 40;
843                         SDL_SetAlpha(surf_b_over, SDL_SRCALPHA, (int)(fadeover*(200 + 55*sin(fadetimer))));
844                         SDL_BlitSurface(surf_b_over,&src,surf_screen,&dest);
845                 break;
846
847                 case TITLE_PAGE:
848
849                         src.w = surf_b_variations->w;
850                         src.h = surf_b_variations->h;
851                         dest.w = src.w;
852                         dest.h = src.h;
853                         dest.x = (XSIZE-src.w)/2 + cos(fadetimer/6.5)*10;
854                         dest.y = (YSIZE/2-src.h)/2 + sin(fadetimer/5.0)*10;
855                         SDL_SetAlpha(surf_b_variations, SDL_SRCALPHA, (int)(200 + 55*sin(fadetimer += movementrate/2.0)));
856                         SDL_BlitSurface(surf_b_variations,&src,surf_screen,&dest);
857
858                         src.w = surf_b_on->w;
859                         src.h = surf_b_on->h;
860                         dest.w = src.w;
861                         dest.h = src.h;
862                         dest.x = (XSIZE-src.w)/2 + cos((fadetimer + 1.0)/6.5)*10;
863                         dest.y = (YSIZE/2-src.h)/2 + surf_b_variations->h + 20 + sin((fadetimer + 1.0)/5.0)*10;
864                         SDL_SetAlpha(surf_b_on, SDL_SRCALPHA, (int)(200 + 55*sin(fadetimer-1.0)));
865                         SDL_BlitSurface(surf_b_on,&src,surf_screen,&dest);
866
867                         src.w = surf_b_rockdodger->w;
868                         src.h = surf_b_rockdodger->h;
869                         dest.w = src.w;
870                         dest.h = src.h;
871                         dest.x = (XSIZE-src.w)/2 + cos((fadetimer + 2.0)/6.5)*10;
872                         dest.y = (YSIZE/2-src.h)/2 + surf_b_variations->h + surf_b_on->h + 40 + sin((fadetimer + 2.0)/5)*10;
873                         SDL_SetAlpha(surf_b_rockdodger, SDL_SRCALPHA, (int)(200 + 55*sin(fadetimer-2.0)));
874                         SDL_BlitSurface(surf_b_rockdodger,&src,surf_screen,&dest);
875
876                         text = "Version " VERSION;
877                         x = (XSIZE-SFont_wide(text))/2 + sin(fadetimer/4.5)*10;
878                         PutString(surf_screen,x,YSIZE-50 + sin(fadetimer/2)*5,text);
879
880                         text = sequence[(int)(fadetimer/40)%NSEQUENCE];
881                         //text = "Press SPACE to start!";
882                         x = (XSIZE-SFont_wide(text))/2 + cos(fadetimer/4.5)*10;
883                         PutString(surf_screen,x,YSIZE-100 + cos(fadetimer/3)*5,text);
884                 break;
885
886                 case HIGH_SCORE_ENTRY:
887
888                         if(score >= high[7].score) {
889                                 play_tune(2);
890                                 if(SFont_Input (surf_screen, 330, 50 + (scorerank + 2)*font_height, 300, name)) {
891                                         // Insert name into high score table
892
893                                         // Lose the lowest name forever (loser!)
894                                         //if(high[7].allocated)
895                                         //      free(high[7].name);                     // THIS WAS CRASHING SO I REMOVED IT
896
897                                         // Insert new high score
898                                         high[scorerank].score = score;
899                                         high[scorerank].name = strdup(name);    // MEMORY NEVER FREED!
900                                         high[scorerank].allocated = 1;
901                         
902                                         // Set the global name string to "", ready for the next winner
903                                         name[0] = 0;
904                         
905                                         // Change state to briefly show high scores page
906                                         state = HIGH_SCORE_DISPLAY;
907                                         state_timeout = 200;
908
909                                         // Write the high score table to the file
910                                         write_high_score_table();
911                         
912                                         // Play the title page tune
913                                         play_tune(0);
914                                 }
915                         } else {
916                                 state = HIGH_SCORE_DISPLAY;
917                                 state_timeout = 400;
918                         }
919                 // FALL THROUGH
920
921                 case HIGH_SCORE_DISPLAY:
922                         // Display de list o high scores mon.
923                         PutString(surf_screen,180,50,"High scores");
924                         for(i = 0; i<8; i++) {
925                                 char s[1024];
926                                 sprintf(s, "#%1d",i + 1);
927                                 PutString(surf_screen, 150, 50 + (i + 2)*font_height,s);
928                                 snprintscore(s, 1024, high[i].score);
929                                 PutString(surf_screen, 200, 50 + (i + 2)*font_height,s);
930                                 sprintf(s, "%3s", high[i].name);
931                                 PutString(surf_screen, 330, 50 + (i + 2)*font_height,s);
932                         }
933
934         }
935
936         if(!gameover && state == GAMEPLAY) {
937                 SDL_LockSurface(surf_screen);
938                 raw_pixels = (Uint16 *) surf_screen->pixels;
939                 // Check that the black points on the ship are
940                 // still black, and not covered up by rocks.
941                 for(p = black_point; p<blackptr; p++) { 
942                         offset = surf_screen->pitch/2 * (p->y + (int)yship) + p->x + (int)xship;
943                         if(raw_pixels[offset]) {
944                                 // Set the bang flag
945                                 bang = 1;
946                         }
947                 }
948                 SDL_UnlockSurface(surf_screen);
949         }
950
951         // Draw all the little ships
952         if(state == GAMEPLAY || state == DEAD_PAUSE || state == GAME_OVER)
953         for(i = 0; i<nships-1; i++) {
954                 src.w = surf_life->w;
955                 src.h = surf_life->h;
956                 dest.w = src.w;
957                 dest.h = src.h;
958                 dest.x = (i + 1)*(src.w + 10);
959                 dest.y = 20;
960                 SDL_BlitSurface(surf_life,&src,surf_screen,&dest);
961         }
962
963
964         // Update the score
965         /*
966         n = SDL_GetTicks()-initticks;
967         if(score)
968         ticks_since_last = n-score;
969         score = n;
970         */
971
972         ticks_since_last = SDL_GetTicks()-last_ticks;
973         last_ticks = SDL_GetTicks();
974         if(ticks_since_last>200 || ticks_since_last<0) {
975                 movementrate = 0;
976         }
977         else {
978                 movementrate = ticks_since_last/50.0;
979                 if(state == GAMEPLAY) {
980                         score += ticks_since_last;
981                 }
982         }
983
984         // Update the surface
985         SDL_Flip(surf_screen);
986
987
988         return bang;
989 }
990 int gameloop() {
991         int i = 0;
992         Uint8 *keystate;
993
994
995         for(;;) {
996                 if(!paused) {
997                         // Count down the game loop timer, and change state when it gets to zero or less;
998
999                         if((state_timeout -= movementrate*3) < 0) {
1000                                 switch(state) {
1001                                         case DEAD_PAUSE:
1002                                                 // Create a new ship and start all over again
1003                                                 state = GAMEPLAY;
1004                                                 play_tune(1);
1005                                                 xvel = 3;
1006                                                 yvel = 0;
1007                                         break;
1008                                         case GAME_OVER:
1009                                                 state = HIGH_SCORE_ENTRY;
1010                                                 clearBuffer();
1011                                                 name[0] = 0;
1012                                                 state_timeout = 5.0e6;
1013
1014                                                 if(score >= high[7].score) {
1015                                                         // Read the high score table from the storage file
1016                                                         read_high_score_table();
1017
1018                                                         // Find ranking of this score, store as scorerank
1019                                                         for(i = 0; i<8; i++) {
1020                                                         if(high[i].score <= score) {
1021                                                                 scorerank = i;
1022                                                                 break;
1023                                                         }
1024                                                         }
1025
1026                                                         // Move all lower scores down a notch
1027                                                         for(i = 7; i >= scorerank; i--)
1028                                                         high[i] = high[i-1];
1029
1030                                                         // Insert blank high score
1031                                                         high[scorerank].score = score;
1032                                                         high[scorerank].name = "";
1033                                                         high[scorerank].allocated = 0;
1034                                                 }
1035
1036                                         break;
1037                                         case HIGH_SCORE_DISPLAY:
1038                                                 state = TITLE_PAGE;
1039                                                 state_timeout = 500.0;
1040                                         break;
1041                                         case HIGH_SCORE_ENTRY:
1042                                                 // state = TITLE_PAGE;
1043                                                 // play_tune(1);
1044                                                 // state_timeout = 100.0;
1045                                         break;
1046                                         case TITLE_PAGE:
1047                                                 state = HIGH_SCORE_DISPLAY;
1048                                                 state_timeout = 200.0;
1049                                         break;
1050                                 }
1051                         } else {
1052                                 if(state == DEAD_PAUSE) {
1053                                         float blast_radius = START_RAD * state_timeout / 50.0;
1054                                         for(i = 0; i<MAXROCKS; i++ ) {
1055                                                 float dx, dy, n;
1056                                                 dx = rock[i].x - xship;
1057                                                 dy = rock[i].y - yship;
1058                                                 n = sqrt(dx*dx + dy*dy);
1059                                                 if(n < blast_radius) {
1060                                                         rock[i].xvel += rockrate*dx/n;
1061                                                         rock[i].yvel += rockrate*dy/n;
1062                                                 }
1063                                         }
1064                                 }
1065                         }
1066
1067                         if(--countdown <= 0 && (rnd()*100.0<(rockrate += 0.025))) {
1068                                 // Create a rock
1069                                 rockptr++;
1070                                 if(rockptr-rock >= MAXROCKS) {
1071                                         rockptr = rock;
1072                                 }
1073                                 if(!rockptr->active) {
1074                                         rockptr->x = (float)XSIZE;
1075                                         rockptr->xvel = -(rockspeed)*(1 + rnd());
1076                                         rockptr->yvel = rnd()-0.5;
1077                                         rockptr->type_number = random() % NROCKS;
1078                                         rockptr->heat = 0;
1079                                         rockptr->image = surf_rock[rockptr->type_number];// [random()%NROCKS];
1080                                         rockptr->active = 1;
1081                                         rockptr->y = rnd()*(YSIZE + rockptr->image->h);
1082                                 }
1083                                 if(movementrate>0.1) {
1084                                         countdown = (int)(ROCKRATE/movementrate);
1085                                 } else {
1086                                         countdown = 0;
1087                                 }
1088                         }
1089
1090                         // FRICTION?
1091                         if(friction) {
1092                                 xvel *= pow((double)0.9,(double)movementrate);
1093                                 yvel *= pow((double)0.9,(double)movementrate);
1094                                 // if(abs(xvel)<0.00001) xvel = 0;
1095                                 // if(abs(yvel)<0.00001) yvel = 0;
1096                         }
1097
1098                         // INERTIA
1099                         xship += xvel*movementrate;
1100                         yship += yvel*movementrate;
1101
1102                         // SCROLLING
1103                         yscroll = yship - (YSIZE / 2);
1104                         yscroll += yvel * 25;
1105                         yscroll /= -25;
1106                         yscroll = ((scrollvel * (12 - movementrate)) + (yscroll * movementrate)) / 12;
1107                         scrollvel = yscroll;
1108                         yscroll = yscroll*movementrate;
1109                         yship += yscroll;
1110                         
1111                         // Move all the rocks
1112                         for(i = 0; i<MAXROCKS; i++) if(rock[i].active) {
1113                                 rock[i].x += rock[i].xvel*movementrate;
1114                                 rock[i].y += rock[i].yvel*movementrate + yscroll;
1115                                 if(rock[i].y > YSIZE) {
1116                                         rock[i].y -= YSIZE;
1117                                         rock[i].y -= rock[i].image->w;
1118                                 } else if(rock[i].y < -rock[i].image->w) {
1119                                         rock[i].y += YSIZE;
1120                                         rock[i].y += rock[i].image->w;
1121                                 }
1122                                 if(rock[i].x < -32.0) rock[i].active = 0;
1123                         }
1124
1125
1126                         // BOUNCE X
1127                         if(xship<0 || xship>XSIZE-surf_ship->w) {
1128                                 // BOUNCE from left and right wall
1129                                 xship -= xvel*movementrate;
1130                                 xvel *= -0.99;
1131                         }
1132
1133                         // BOUNCE Y
1134                         if(yship<0 || yship>YSIZE-surf_ship->h) {
1135                                 // BOUNCE from top and bottom wall
1136                                 yship -= yvel;
1137                                 yvel *= -0.99;
1138                         }
1139
1140
1141                         if(draw() && state == GAMEPLAY) {
1142                                 if(oss_sound_flag) {
1143                                         // Play the explosion sound
1144                                         play_sound(0);
1145                                 }
1146                                 makebangdots(xship,yship,xvel,yvel,surf_ship,30);
1147                                 if(--nships <= 0) {
1148                                         gameover = 1;
1149                                         state = GAME_OVER;
1150                                         state_timeout = 200.0;
1151                                         fadetimer = 0.0;
1152                                         faderate = movementrate;
1153                                 }
1154                                 else {
1155                                         state = DEAD_PAUSE;
1156                                         state_timeout = 50.0;
1157                                         xship = 10;
1158                                         yship = YSIZE/2;
1159                                         xvel = 0;
1160                                         yvel = 0;
1161                                 }
1162                         }
1163
1164                         SDL_PumpEvents();
1165                         keystate = SDL_GetKeyState(NULL);
1166
1167                         if(state != HIGH_SCORE_ENTRY && (keystate[SDLK_q] || keystate[SDLK_ESCAPE])) {
1168                                 return 0;
1169                         }
1170
1171                         if(keystate[SDLK_SPACE] && (state == HIGH_SCORE_DISPLAY || state == TITLE_PAGE || state == DEMO)) {
1172
1173                                 for(i = 0; i<MAXROCKS; i++ ) {
1174                                         rock[i].active = 0;
1175                                 }
1176
1177                                 rockrate = 54.0;
1178                                 rockspeed = 5.0;
1179
1180                                 nships = 4;
1181                                 score = 0;
1182
1183                                 state = GAMEPLAY;
1184                                 play_tune(1);
1185
1186                                 xvel = -1;
1187                                 gameover = 0;
1188                                 yvel = 0;
1189                                 xship = 0;
1190                                 yship = YSIZE/2;
1191
1192                         }
1193
1194                         maneuver = 0;
1195                 } else {
1196                         SDL_PumpEvents();
1197                         keystate = SDL_GetKeyState(NULL);
1198                 }
1199
1200                 if(state == GAMEPLAY) {
1201                         if(!gameover) {
1202
1203                                 if(!paused) {
1204                                         if(keystate[SDLK_UP] | keystate[SDLK_c])                { yvel -= 1.5*movementrate; maneuver |= 1<<3;}
1205                                         if(keystate[SDLK_DOWN] | keystate[SDLK_t])              { yvel += 1.5*movementrate; maneuver |= 1<<1;}
1206                                         if(keystate[SDLK_LEFT] | keystate[SDLK_h])              { xvel -= 1.5*movementrate; maneuver |= 1<<2;}
1207                                         if(keystate[SDLK_RIGHT] | keystate[SDLK_n])             { xvel += 1.5*movementrate; maneuver |= 1;}
1208                                         if(keystate[SDLK_3])            { SDL_SaveBMP(surf_screen, "snapshot.bmp"); }
1209                                 }
1210
1211                                 if(keystate[SDLK_p] | keystate[SDLK_s]) {
1212                                         if(!pausedown) {
1213                                                 paused = !paused;
1214                                                 if(paused) {
1215                                                         SDL_Rect src,dest;
1216                                                         src.w = surf_b_variations->w;
1217                                                         src.h = surf_b_variations->h;
1218                                                         dest.w = src.w;
1219                                                         dest.h = src.h;
1220                                                         dest.x = (XSIZE-src.w)/2;
1221                                                         dest.y = (YSIZE-src.h)/2;
1222                                                         SDL_BlitSurface(surf_b_variations,&src,surf_screen,&dest);
1223                                                         // Update the surface
1224                                                         SDL_Flip(surf_screen);
1225                                                 }
1226                                                 pausedown = 1;
1227                                         }
1228                                 } else {
1229                                         pausedown = 0;
1230                                 }
1231
1232                         }
1233                         else {
1234                                 paused = 0;
1235                                 pausedown = 0;
1236                         }
1237                 } else if(state == GAME_OVER) {
1238                         if(keystate[SDLK_SPACE]) {
1239                                 state_timeout = -1;
1240                         }
1241                 }
1242         }
1243 }
1244 main(int argc, char **argv) {
1245         int i, x, fullscreen;
1246
1247         fullscreen = 0;
1248         tail_plume = 0;
1249         friction = 0;
1250         oss_sound_flag = 1;
1251
1252         while ((x = getopt(argc,argv,"efhsp")) >= 0) {
1253                 switch(x) {
1254                         case 'e': // engine
1255                                 tail_plume = 1;
1256                         break;
1257                         case 'f': // fullscreen
1258                                 fullscreen = 1;
1259                         break;
1260                         case 'h': // help
1261                                 printf("Variations on RockDodger\n"
1262                                        " -e big tail [E]ngine\n"
1263                                        " -f [F]ull screen\n"
1264                                        " -h this [H]elp message\n"
1265                                        " -p original [P]hysics (friction)\n"
1266                                        " -s [S]ilent (no sound)\n");
1267                                 exit(0);
1268                         break;
1269                         case 'p': // physics
1270                                 friction = 1;
1271                         break;
1272                         case 's': // silent
1273                                 oss_sound_flag = 0;
1274                         break;
1275                 }
1276         }
1277
1278         if(init(fullscreen)) {
1279                 printf ("ta: '%s'\n",initerror);
1280                 return 1;
1281         }
1282
1283         while(1) {
1284                 for(i = 0; i<MAXROCKS; i++) {
1285                         rock[i].active = 0;
1286                 }
1287                 rockrate = 54.0;
1288                 rockspeed = 5.0;
1289                 initticks = SDL_GetTicks();
1290                 if(gameloop() == 0) {
1291                         break;
1292                 }
1293                 printf ("score = %d\n",score);
1294                 SDL_Delay(1000);
1295         }
1296
1297         return 0;
1298 }