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