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