JasonWoof Got questions, comments, patches, etc.? Contact Jason Woofenden
* main.c: removed a whole bunch of unnecessary src rects for SDL blits.
[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 #ifdef DEBUG
23 #include "debug.h"
24 #endif
25
26 #include "config.h"
27 #include "file.h"
28 #include "globals.h"
29 #include "rocks.h"
30 #include "score.h"
31 #include "shape.h"
32 #include "sound.h"
33
34 #include <math.h>
35 #include <SDL/SDL.h>
36 #include <SDL/SDL_image.h>
37 #include <stdarg.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
42
43 #include "SFont.h"
44
45 // ************************************* VARS
46 // SDL_Surface global variables
47 SDL_Surface 
48         *surf_screen,   // Screen
49         *surf_b_variations, // "variations" banner
50         *surf_b_on, // "on" banner
51         *surf_b_rockdodger, // "rockdodger" banner
52         *surf_b_game,   // Title element "game"
53         *surf_b_over,   // Title element "over"
54         *surf_ship,             // Spaceship element
55         *surf_life,     // Indicator of number of ships remaining
56         *surf_speed, // Speed indicator
57         *surf_rock[NROCKS],     // THE ROCKS
58         *surf_font_big; // The big font
59         
60
61 SFont_Font *g_font;
62
63 // Structure global variables
64 struct enginedots edot[MAXENGINEDOTS], *dotptr = edot;
65 struct bangdots bdot[MAXBANGDOTS], *bdotptr = bdot;
66 struct spacedot sdot[MAXSPACEDOTS];
67
68 // Other global variables
69 char topline[1024];
70 char *initerror = "";
71
72 struct shape shipshape;
73 float shipx = XSIZE/2, shipy = YSIZE/2; // X position, 0..XSIZE
74 float shipdx = 8, shipdy = 0;   // Change in X position per tick.
75 float screendx = 7.5, screendy = 0.0;
76 float xscroll, yscroll;
77 float gamerate;  // this controls the speed of everything that moves.
78
79 float bangx, bangy, bangdx, bangdy;
80
81 float game_dist, avg_speed, cur_speed;
82 uint16_t avg_speed_w, cur_speed_w; // [0, 74]
83
84 int nships,score,game_ticks,ticks_since_last,last_ticks;
85 int gameover;
86 int maneuver = 0;
87 int sound_flag, music_flag;
88 int tail_plume; // display big engine at the back?
89 int friction;   // should there be friction?
90 float fadetimer = 0, faderate;
91
92 int pausedown = 0, paused = 0;
93
94 // bangdot start (bd1) and end (bd2) position:
95 int bd1 = 0, bd2 = 0;
96
97 enum states {
98         TITLE_PAGE,
99         GAMEPLAY,
100         DEAD_PAUSE,
101         GAME_OVER,
102         HIGH_SCORE_ENTRY,
103         HIGH_SCORE_DISPLAY
104 };
105 enum states state = TITLE_PAGE;
106 float state_timeout = 600.0;
107
108 #define NSEQUENCE 2
109 char *sequence[] = {
110         "Press SPACE to start",
111         "http://qualdan.com/vor/"
112 };
113
114 int bangdotlife, nbangdots;
115 Uint16 heatcolor[W*3];
116
117 char *data_dir;
118 extern char *optarg;
119 extern int optind, opterr, optopt;
120
121 // ************************************* FUNCS
122
123 float
124 rnd() {
125         return (float)random()/(float)RAND_MAX;
126 }
127
128 void
129 init_engine_dots() {
130         int i;
131         for(i = 0; i<MAXENGINEDOTS; i++) {
132                 edot[i].active = 0;
133         }
134 }
135
136 void
137 init_space_dots() {
138         int i,b;
139         for(i = 0; i<MAXSPACEDOTS; i++) {
140                 sdot[i].x = rnd()*(XSIZE-5);
141                 sdot[i].y = rnd()*(YSIZE-5);
142                 sdot[i].z = MAXDUSTDEPTH*sqrt(rnd());
143                 b = (MAXDUSTDEPTH - sdot[i].z) * 255.0 / MAXDUSTDEPTH;
144                 sdot[i].color = SDL_MapRGB(surf_screen->format, b, b, b);
145         }
146 }
147
148 void
149 make_bang_dots(int xbang, int ybang, int dx, int dy, SDL_Surface *s, int power) {
150
151         // TODO - stop generating dots after a certain amount of time has passed, to cope with slower CPUs.
152         // TODO - generate and display dots in a circular buffer
153
154         int x,y,endcount;
155         Uint16 *rawpixel,c;
156         double theta,r;
157         int begin_generate;
158
159         begin_generate = SDL_GetTicks();
160
161         SDL_LockSurface(s);
162         rawpixel = (Uint16 *) s->pixels;
163
164         //for(n = 0; n <= power/2; n++) {
165
166         endcount = 0;
167         while (endcount<3) {
168                 for(x = 0; x<s->w; x++) {
169                         for(y = 0; y<s->h; y++) {
170                                 c = rawpixel[s->pitch/2*y + x];
171                                 if(c && c != s->format->colorkey) {
172
173                                         theta = rnd()*M_PI*2;
174
175                                         r = 1-(rnd()*rnd());
176
177                                         bdot[bd2].dx = (power/50.0)*45.0*cos(theta)*r + dx;
178                                         bdot[bd2].dy = (power/50.0)*45.0*sin(theta)*r + dy;
179                                         bdot[bd2].x = x + xbang;
180                                         bdot[bd2].y = y + ybang;
181
182                                         // Replace the last few bang dots with the pixels from the exploding object
183                                         bdot[bd2].c = (endcount>0)?c:0;
184                                         bdot[bd2].life = 100;
185                                         bdot[bd2].decay = rnd()*3 + 1;
186                                         bdot[bd2].active = 1;
187
188                                         bd2++;
189                                         bd2 %= MAXBANGDOTS;
190
191                                         // If the circular buffer is filled, who cares? They've had their chance.
192                                         //if(bd2 == bd1-1) goto exitloop;
193
194                                 }
195                         }
196                 }
197
198                 if(SDL_GetTicks() - begin_generate > 7) endcount++;
199         }
200
201         SDL_UnlockSurface(s);
202
203 }
204
205 void
206 draw_bang_dots(SDL_Surface *s) {
207         int i;
208         int first_i, last_i;
209         Uint16 *rawpixel;
210         rawpixel = (Uint16 *) s->pixels;
211
212         first_i = -1;
213
214         for(i = bd1; (bd1 <= bd2)?(i<bd2):(i >= bd1 && i < bd2); last_i = ++i) {
215
216                 i %= MAXBANGDOTS;
217
218                 if(bdot[i].x <= 0 || bdot[i].x >= XSIZE || bdot[i].y <= 0 || bdot[i].y >= YSIZE) {
219                         // If the dot has drifted outside the perimeter, kill it
220                         bdot[i].active = 0;
221                 }
222
223                 if(bdot[i].active) {
224                         if(first_i < 0)
225                         first_i = i;
226                         //last_i = i + 1;
227                         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)];
228                         bdot[i].life -= bdot[i].decay;
229                         bdot[i].x += bdot[i].dx*gamerate - xscroll;
230                         bdot[i].y += bdot[i].dy*gamerate - yscroll;
231
232                         if(bdot[i].life<0)
233                         bdot[i].active = 0;
234                 }
235         }
236
237         if(first_i >= 0) {
238                 bd1 = first_i;
239                 bd2 = last_i;
240         }
241         else {
242                 bd1 = 0;
243                 bd2 = 0;
244         }
245
246 }
247
248
249 void
250 draw_space_dots(SDL_Surface *s) {
251         int i;
252         Uint16 *rawpixel;
253         rawpixel = (Uint16 *) s->pixels;
254
255         for(i = 0; i<MAXSPACEDOTS; i++) {
256                 if(sdot[i].y<0) {
257                         sdot[i].y = 0;
258                 }
259                 rawpixel[(int)(s->pitch/2*(int)sdot[i].y) + (int)(sdot[i].x)] = sdot[i].color;
260                 sdot[i].x -= xscroll / (1.3 + sdot[i].z);
261                 sdot[i].y -= yscroll / (1.3 + sdot[i].z);
262                 if(sdot[i].y >= XSIZE) sdot[i].x -= XSIZE;
263                 else if(sdot[i].x < 0) sdot[i].x = XSIZE-1;
264                 if(sdot[i].y > YSIZE) sdot[i].y -= YSIZE;
265                 else if(sdot[i].y < 0) sdot[i].y += YSIZE-1;
266         }
267 }
268
269 void
270 draw_engine_dots(SDL_Surface *s) {
271         int i;
272         Uint16 *rawpixel;
273         rawpixel = (Uint16 *) s->pixels;
274
275         for(i = 0; i<MAXENGINEDOTS; i++) {
276                 if(edot[i].active) {
277                         edot[i].x += edot[i].dx*gamerate - xscroll;
278                         edot[i].y += edot[i].dy*gamerate - yscroll;
279                         if((edot[i].life -= gamerate*3)<0 || edot[i].y<0 || edot[i].y>YSIZE) {
280                                 edot[i].active = 0;
281                         } else if(edot[i].x<0 || edot[i].x>XSIZE) {
282                                 edot[i].active = 0;
283                         } else {
284                                 int heatindex;
285                                 heatindex = edot[i].life * 6;
286                                 //rawpixel[(int)(s->pitch/2*(int)(edot[i].y)) + (int)(edot[i].x)] = lifecolor[(int)(edot[i].life)];
287                                 rawpixel[(int)(s->pitch/2*(int)(edot[i].y)) + (int)(edot[i].x)] = heatindex>3*W ? heatcolor[3*W-1] : heatcolor[heatindex];
288                         }
289                 }
290         }
291 }
292
293 void
294 create_engine_dots(int newdots) {
295         int i;
296         double theta,r,dx,dy;
297
298         if(!tail_plume) return;
299
300         if(state == GAMEPLAY) {
301                 for(i = 0; i<newdots*gamerate; i++) {
302                         if(dotptr->active == 0) {
303                                 theta = rnd()*M_PI*2;
304                                 r = rnd();
305                                 dx = cos(theta)*r;
306                                 dy = sin(theta)*r;
307
308                                 dotptr->active = 1;
309                                 dotptr->x = shipx + surf_ship->w/2-14;
310                                 dotptr->y = shipy + surf_ship->h/2 + (rnd()-0.5)*5-1;
311                                 dotptr->dx = 10*(dx-1.5) + shipdx;
312                                 dotptr->dy = 1*dy + shipdy;
313                                 dotptr->life = 45 + rnd(1)*5;
314
315                                 dotptr++;
316                                 if(dotptr-edot >= MAXENGINEDOTS) {
317                                         dotptr = edot;
318                                 }
319                         }
320                 }
321         }
322 }
323
324 void
325 create_engine_dots2(int newdots, int m) {
326         int i;
327         double theta, theta2, dx, dy, adx, ady;
328
329         // Don't create fresh engine dots when
330         // the game is not being played and a demo is not beng shown
331         if(state != GAMEPLAY) return;
332
333         for(i = 0; i<newdots; i++) {
334                 if(dotptr->active == 0) {
335                         theta = rnd()*M_PI*2;
336                         theta2 = rnd()*M_PI*2;
337
338                         dx = cos(theta) * fabs(cos(theta2));
339                         dy = sin(theta) * fabs(cos(theta2));
340                         adx = fabs(dx);
341                         ady = fabs(dy);
342
343
344                         dotptr->active = 1;
345                         dotptr->x = shipx + surf_ship->w/2 + (rnd()-0.5)*3;
346                         dotptr->y = shipy + surf_ship->h/2 + (rnd()-0.5)*3;
347
348                         switch(m) {
349                                 case 0:
350                                         dotptr->x -= 14;
351                                         dotptr->dx = -20*adx + shipdx;
352                                         dotptr->dy = 2*dy + shipdy;
353                                         dotptr->life = 60 * adx;
354                                 break;
355                                 case 1:
356                                         dotptr->dx = 2*dx + shipdx;
357                                         dotptr->dy = -20*ady + shipdy;
358                                         dotptr->life = 60 * ady;
359                                 break;
360                                 case 2:
361                                         dotptr->x += 14;
362                                         dotptr->dx = 20*adx + shipdx;
363                                         dotptr->dy = 2*dy + shipdy;
364                                         dotptr->life = 60 * adx;
365                                 break;
366                                 case 3:
367                                         dotptr->dx = 2*dx + shipdx;
368                                         dotptr->dy = 20*ady + shipdy;
369                                         dotptr->life = 60 * ady;
370                                 break;
371                         }
372                         dotptr++;
373                         if(dotptr-edot >= MAXENGINEDOTS) {
374                                 dotptr = edot;
375                         }
376                 }
377         }
378 }
379
380 void
381 drawdots(SDL_Surface *s) {
382         int m;
383
384         // Create more engine dots comin' out da back
385         if(!gameover) create_engine_dots(200);
386
387         // Create engine dots out the side we're moving from
388         for(m = 0; m<4; m++) {
389                 if(maneuver & 1<<m) { // 'maneuver' is a bit field
390                         create_engine_dots2(80,m);
391                 }
392         }
393
394         SDL_LockSurface(s);
395         draw_space_dots(s);
396         draw_engine_dots(s);
397         draw_bang_dots(s);
398         SDL_UnlockSurface(s);
399 }
400
401 int
402 init(int fullscreen) {
403
404         int i;
405         SDL_Surface *temp;
406         Uint32 flag;
407
408         // Where are our data files?
409         if(!find_files()) exit(1);
410         read_high_score_table();
411
412         if(sound_flag) {
413                 // Initialize SDL with audio and video
414                 if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) != 0) {
415                         sound_flag = 0;
416                         printf ("Can't open sound, starting without it\n");
417                         atexit(SDL_Quit);
418                 } else {
419                         atexit(SDL_Quit);
420                         atexit(SDL_CloseAudio);
421                         sound_flag = init_sound();
422                 }
423         } else {
424                 // Initialize with video only
425                 CONDERROR(SDL_Init(SDL_INIT_VIDEO) != 0);
426                 atexit(SDL_Quit);
427         }
428
429         play_tune(0);
430
431         // Attempt to get the required video size
432         flag = SDL_DOUBLEBUF | SDL_HWSURFACE;
433         if(fullscreen) flag |= SDL_FULLSCREEN;
434         surf_screen = SDL_SetVideoMode(XSIZE,YSIZE,16,flag);
435
436         // Set the title bar text
437         SDL_WM_SetCaption("Variations on Rockdodger", "VoR");
438
439         NULLERROR(surf_screen);
440
441         // Set the heat color from the range 0 (cold) to 300 (blue-white)
442         for(i = 0; i<W*3; i++) {
443                 heatcolor[i] = SDL_MapRGB(
444                         surf_screen->format,
445                         (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?
446                 );
447         }
448
449         // Load the banners
450         NULLERROR(temp = IMG_Load(add_path("banners/variations.png")));
451         NULLERROR(surf_b_variations = SDL_DisplayFormat(temp));
452
453         NULLERROR(temp = IMG_Load(add_path("banners/on.png")));
454         NULLERROR(surf_b_on = SDL_DisplayFormat(temp));
455
456         NULLERROR(temp = IMG_Load(add_path("banners/rockdodger.png")));
457         NULLERROR(surf_b_rockdodger = SDL_DisplayFormat(temp));
458
459         NULLERROR(temp = IMG_Load(add_path("banners/game.png")));
460         NULLERROR(surf_b_game = SDL_DisplayFormat(temp));
461
462         NULLERROR(temp = IMG_Load(add_path("banners/over.png")));
463         NULLERROR(surf_b_over = SDL_DisplayFormat(temp));
464
465         surf_font_big = IMG_Load(add_path(BIG_FONT_FILE));
466         g_font = SFont_InitFont(surf_font_big);
467
468         // Load the spaceship graphic.
469         NULLERROR(temp = IMG_Load(add_path("sprites/ship.png")));
470         NULLERROR(surf_ship = SDL_DisplayFormat(temp));
471         get_shape(surf_ship, &shipshape);
472
473         // Load the life indicator (small ship) graphic.
474         NULLERROR(temp = IMG_Load(add_path("indicators/life.png")));
475         NULLERROR(surf_life = SDL_DisplayFormat(temp));
476
477         NULLERROR(temp = IMG_Load(add_path("indicators/speed.png")));
478         NULLERROR(surf_speed = SDL_DisplayFormat(temp));
479
480         init_engine_dots();
481         init_space_dots();
482
483         init_rocks();
484
485         // Remove the mouse cursor
486 #ifdef SDL_DISABLE
487         SDL_ShowCursor(SDL_DISABLE);
488 #endif
489
490         return 0;
491 }
492
493 int
494 draw() {
495         int i;
496         SDL_Rect src, dest;
497         int bang, x;
498         char *text;
499         float fadegame,fadeover;
500
501         bang = 0;
502
503         // Draw a fully black background
504         SDL_FillRect(surf_screen,NULL,0);
505
506         // Draw the background dots
507         drawdots(surf_screen);
508
509         // Draw ship
510         if(!gameover && state == GAMEPLAY ) {
511                 dest.x = shipx;
512                 dest.y = shipy;
513                 SDL_BlitSurface(surf_ship,NULL,surf_screen,&dest);
514         }
515
516         draw_rocks();
517
518         // Draw the life indicators.
519         if(state == GAMEPLAY || state == DEAD_PAUSE || state == GAME_OVER)
520         for(i = 0; i<nships-1; i++) {
521                 dest.x = (i + 1)*(surf_life->w + 10);
522                 dest.y = 20;
523                 SDL_BlitSurface(surf_life, NULL, surf_screen, &dest);
524         }
525
526         if(state == GAMEPLAY) {
527                 // Update speeds.
528                 cur_speed = shipdx;
529                 if(shipdx < 0) cur_speed = 0;
530                 if(shipdx > 20) cur_speed = 20;
531                 game_dist += cur_speed*ticks_since_last;
532                 game_ticks += ticks_since_last;
533                 if(game_ticks < 2*1000) avg_speed = cur_speed;
534                 else avg_speed = game_dist/game_ticks;
535                 // printf("avg=%.2f, cur=%.2f.\n", avg_speed, cur_speed);
536                 avg_speed_w = 10 + 64*avg_speed/20;
537                 cur_speed_w = 10 + 64*cur_speed/20;
538         }
539
540         if(state == GAMEPLAY || state == DEAD_PAUSE) {
541                 // Draw the speed indicators.
542                 src.x = 0; src.y = 0;
543                 src.h = surf_speed->h;
544                 dest.x = 240;
545                 src.w = avg_speed_w; dest.y = 10;
546                 SDL_BlitSurface(surf_speed, &src, surf_screen, &dest);
547                 src.w = cur_speed_w; dest.y = 20;
548                 SDL_BlitSurface(surf_speed, &src, surf_screen, &dest);
549         }
550
551         // Draw the score
552         snprintscore_line(topline, 50, score);
553         SFont_Write(surf_screen, g_font, XSIZE-250, 0, topline);
554
555         // If it's game over, show the game over graphic in the dead centre
556         switch (state) {
557                 case GAME_OVER:
558                         if(fadetimer<3.0/faderate) {
559                                 fadegame = fadetimer/(3.0/faderate);
560                         } else {
561                                 fadegame = 1.0;
562                         }
563
564                         if(fadetimer<3.0/faderate) {
565                                 fadeover = 0.0;
566                         } else if(fadetimer<6.0/faderate) {
567                                 fadeover = ((3.0/faderate)-fadetimer)/(6.0/faderate);
568                         } else {
569                                 fadeover = 1.0;
570                         }
571
572                         dest.x = (XSIZE-surf_b_game->w)/2;
573                         dest.y = (YSIZE-surf_b_game->h)/2-40;
574                         SDL_SetAlpha(surf_b_game, SDL_SRCALPHA, (int)(fadegame*(200 + 55*cos(fadetimer += gamerate/1.0))));
575                         SDL_BlitSurface(surf_b_game,NULL,surf_screen,&dest);
576
577                         dest.x = (XSIZE-surf_b_over->w)/2;
578                         dest.y = (YSIZE-surf_b_over->h)/2 + 40;
579                         SDL_SetAlpha(surf_b_over, SDL_SRCALPHA, (int)(fadeover*(200 + 55*sin(fadetimer))));
580                         SDL_BlitSurface(surf_b_over,NULL,surf_screen,&dest);
581                 break;
582
583                 case TITLE_PAGE:
584
585                         dest.x = (XSIZE-surf_b_variations->w)/2 + cos(fadetimer/6.5)*10;
586                         dest.y = (YSIZE/2-surf_b_variations->h)/2 + sin(fadetimer/5.0)*10;
587                         SDL_SetAlpha(surf_b_variations, SDL_SRCALPHA, (int)(200 + 55*sin(fadetimer += gamerate/2.0)));
588                         SDL_BlitSurface(surf_b_variations,NULL,surf_screen,&dest);
589
590                         dest.x = (XSIZE-surf_b_on->w)/2 + cos((fadetimer + 1.0)/6.5)*10;
591                         dest.y = (YSIZE/2-surf_b_on->h)/2 + surf_b_variations->h + 20 + sin((fadetimer + 1.0)/5.0)*10;
592                         SDL_SetAlpha(surf_b_on, SDL_SRCALPHA, (int)(200 + 55*sin(fadetimer-1.0)));
593                         SDL_BlitSurface(surf_b_on,NULL,surf_screen,&dest);
594
595                         dest.x = (XSIZE-surf_b_rockdodger->w)/2 + cos((fadetimer + 2.0)/6.5)*10;
596                         dest.y = (YSIZE/2-surf_b_rockdodger->h)/2 + surf_b_variations->h + surf_b_on->h + 40 + sin((fadetimer + 2.0)/5)*10;
597                         SDL_SetAlpha(surf_b_rockdodger, SDL_SRCALPHA, (int)(200 + 55*sin(fadetimer-2.0)));
598                         SDL_BlitSurface(surf_b_rockdodger,NULL,surf_screen,&dest);
599
600                         text = "Version " VERSION;
601                         x = (XSIZE-SFont_TextWidth(g_font,text))/2 + sin(fadetimer/4.5)*10;
602                         SFont_Write(surf_screen,g_font,x,YSIZE-50 + sin(fadetimer/2)*5,text);
603
604                         text = sequence[(int)(fadetimer/40)%NSEQUENCE];
605                         //text = "Press SPACE to start!";
606                         x = (XSIZE-SFont_TextWidth(g_font,text))/2 + cos(fadetimer/4.5)*10;
607                         SFont_Write(surf_screen,g_font,x,YSIZE-100 + cos(fadetimer/3)*5,text);
608                 break;
609
610                 case HIGH_SCORE_ENTRY:
611                         play_tune(2);
612                         if(!process_score_input()) {  // done inputting name
613
614                                 // Change state to briefly show high scores page
615                                 state = HIGH_SCORE_DISPLAY;
616                                 state_timeout = 200;
617
618                                 // Write the high score table to the file
619                                 write_high_score_table();
620                 
621                                 // Play the title page tune
622                                 play_tune(0);
623                         }
624                 // FALL THROUGH TO
625                 case HIGH_SCORE_DISPLAY:
626                         // Display de list o high scores mon.
627                         display_scores(surf_screen, 150,50);
628                         break;
629                 case GAMEPLAY:
630                 case DEAD_PAUSE:
631                         ; // no action necessary
632         }
633
634         if(!gameover && state == GAMEPLAY) {
635                 bang = hit_rocks(shipx, shipy, &shipshape);
636         }
637
638         ticks_since_last = SDL_GetTicks()-last_ticks;
639         last_ticks = SDL_GetTicks();
640         if(ticks_since_last>200 || ticks_since_last<0) {
641                 gamerate = 0;
642         }
643         else {
644                 gamerate = GAMESPEED*ticks_since_last/50.0;
645                 if(state == GAMEPLAY) {
646                         score += ticks_since_last;
647                 }
648         }
649
650         // Update the surface
651         SDL_Flip(surf_screen);
652
653
654         return bang;
655 }
656
657 int
658 gameloop() {
659         Uint8 *keystate;
660         float tmp;
661
662
663         for(;;) {
664                 if(!paused) {
665                         // Count down the game loop timer, and change state when it gets to zero or less;
666
667                         if((state_timeout -= gamerate*3) < 0) {
668                                 switch(state) {
669                                         case DEAD_PAUSE:
670                                                 // Create a new ship and start all over again
671                                                 state = GAMEPLAY;
672                                                 play_tune(1);
673                                                 break;
674                                         case GAME_OVER:
675                                                 state = HIGH_SCORE_ENTRY;
676                                                 state_timeout = 5.0e6;
677                                                 if(new_high_score(score)) {
678                                                         SDL_Event e;
679                                                         SDL_EnableUNICODE(1);
680                                                         while(SDL_PollEvent(&e))
681                                                                 ;
682                                                 } else {
683                                                         state = HIGH_SCORE_DISPLAY;
684                                                         state_timeout = 400;
685                                                 }
686                                                 break;
687                                         case HIGH_SCORE_DISPLAY:
688                                                 state = TITLE_PAGE;
689                                                 state_timeout = 500.0;
690                                                 break;
691                                         case HIGH_SCORE_ENTRY:
692                                                 // state = TITLE_PAGE;
693                                                 // play_tune(1);
694                                                 // state_timeout = 100.0;
695                                                 break;
696                                         case TITLE_PAGE:
697                                                 state = HIGH_SCORE_DISPLAY;
698                                                 state_timeout = 200.0;
699                                                 break;
700                                         case GAMEPLAY:
701                                                 ; // no action necessary
702                                 }
703                         } else {
704                                 if(state == DEAD_PAUSE) {
705                                         float blast_radius;
706                                         int fixonly;
707
708                                         if(state_timeout < DEAD_PAUSE_LENGTH - 20.0) {
709                                                 blast_radius = BLAST_RADIUS * 1.3;
710                                                 fixonly = 1;
711                                         } else {
712                                                 blast_radius = BLAST_RADIUS * (DEAD_PAUSE_LENGTH - state_timeout) / 20.0;
713                                                 fixonly = 0;
714                                         }
715                                         blast_rocks(bangx, bangy, blast_radius, fixonly);
716
717                                         if(bangx < 60) bangx = 60;
718                                 }
719                         }
720
721                         new_rocks();
722
723                         // FRICTION?
724                         if(friction) {
725                                 shipdx *= pow((double)0.9,(double)gamerate);
726                                 shipdy *= pow((double)0.9,(double)gamerate);
727                         }
728
729                         // INERTIA
730                         shipx += shipdx*gamerate;
731                         shipy += shipdy*gamerate;
732
733                         // SCROLLING
734                         tmp = shipy - (YSIZE / 2);
735                         tmp += shipdy * 25;
736                         tmp /= -25;
737                         tmp = ((screendy * (gamerate - 12)) + (tmp * gamerate)) / 12;
738                         screendy = -tmp;
739                         tmp = shipx - (XSIZE / 3);
740                         tmp += shipdx * 25;
741                         tmp /= -25;
742                         tmp = ((screendx * (gamerate - 12)) + (tmp * gamerate)) / 12;
743                         screendx = -tmp;
744
745                         xscroll = screendx * gamerate;
746                         yscroll = screendy * gamerate;
747                         shipx -= xscroll;
748                         shipy -= yscroll;
749
750                         // move bang center
751                         bangx += bangdx*gamerate - xscroll;
752                         bangy += bangdy*gamerate - yscroll;
753
754                         move_rocks();
755
756
757                         // BOUNCE X
758                         if(shipx<0 || shipx>XSIZE-surf_ship->w) {
759                                 // BOUNCE from left and right wall
760                                 shipx -= shipdx*gamerate;
761                                 shipdx *= -99;
762                         }
763
764                         // BOUNCE Y
765                         if(shipy<0 || shipy>YSIZE-surf_ship->h) {
766                                 // BOUNCE from top and bottom wall
767                                 shipy -= shipdy;
768                                 shipdy *= -0.99;
769                         }
770
771
772                         if(draw() && state == GAMEPLAY) {
773                                 // Died
774                                 play_sound(0); // Play the explosion sound
775                                 bangx = shipx; bangy = shipy; bangdx = shipdx; bangdy = shipdy;
776                                 make_bang_dots(shipx,shipy,shipdx,shipdy,surf_ship,30);
777                                 shipdx *= 0.5; shipdy *= 0.5;
778                                 if(--nships <= 0) {
779                                         state = GAME_OVER;
780                                         gameover = 1;
781                                         shipdx = 8; shipdy = 0;
782                                         state_timeout = 200.0;
783                                         fadetimer = 0.0;
784                                         faderate = gamerate;
785                                 }
786                                 else {
787                                         state = DEAD_PAUSE;
788                                         state_timeout = DEAD_PAUSE_LENGTH;
789                                 }
790                         }
791
792                         SDL_PumpEvents();
793                         keystate = SDL_GetKeyState(NULL);
794
795                         if(keystate[SDLK_SPACE] && (state == HIGH_SCORE_DISPLAY || state == TITLE_PAGE)) {
796
797                                 reset_rocks();
798
799                                 game_ticks = 0;
800
801                                 nships = 4;
802                                 score = 0;
803
804                                 state = GAMEPLAY;
805                                 play_tune(1);
806
807                                 gameover = 0;
808                                 shipx = XSIZE/2.2; shipy = YSIZE/2;
809                                 shipdx = screendx; shipdy = screendy;
810                         }
811
812                         maneuver = 0;
813                 } else {
814                         SDL_PumpEvents();
815                         keystate = SDL_GetKeyState(NULL);
816                 }
817
818                 if(state == GAMEPLAY) {
819                         if(!gameover) {
820
821                                 if(!paused) {
822                                         if(keystate[SDLK_UP] | keystate[SDLK_c])                { shipdy -= 1.5*gamerate; maneuver |= 1<<3;}
823                                         if(keystate[SDLK_DOWN] | keystate[SDLK_t])              { shipdy += 1.5*gamerate; maneuver |= 1<<1;}
824                                         if(keystate[SDLK_LEFT] | keystate[SDLK_h])              { shipdx -= 1.5*gamerate; maneuver |= 1<<2;}
825                                         if(keystate[SDLK_RIGHT] | keystate[SDLK_n])             { shipdx += 1.5*gamerate; maneuver |= 1;}
826                                         if(keystate[SDLK_3])            { SDL_SaveBMP(surf_screen, "snapshot.bmp"); }
827                                 }
828
829                                 if(keystate[SDLK_p] | keystate[SDLK_s]) {
830                                         if(!pausedown) {
831                                                 paused = !paused;
832                                                 pausedown = 1;
833                                         }
834                                 } else {
835                                         pausedown = 0;
836                                 }
837
838                         }
839                         else {
840                                 paused = 0;
841                                 pausedown = 0;
842                         }
843                 } else if(state == GAME_OVER) {
844                         if(keystate[SDLK_SPACE]) {
845                                 state_timeout = -1;
846                         }
847                 }
848
849                 if(state != HIGH_SCORE_ENTRY && (keystate[SDLK_q] || keystate[SDLK_ESCAPE])) {
850                         return 0;
851                 }
852
853         }
854 }
855
856 int
857 main(int argc, char **argv) {
858         int x, fullscreen;
859
860         fullscreen = 0;
861         tail_plume = 0;
862         friction = 0;
863         sound_flag = 1;
864         music_flag = 0;
865
866         while ((x = getopt(argc,argv,"efhmps")) >= 0) {
867                 switch(x) {
868                         case 'e': // engine
869                                 tail_plume = 1;
870                         break;
871                         case 'f': // fullscreen
872                                 fullscreen = 1;
873                         break;
874                         case 'h': // help
875                                 printf("Variations on RockDodger\n"
876                                        " -e big tail [E]ngine\n"
877                                        " -f [F]ull screen\n"
878                                        " -h this [H]elp message\n"
879                                        " -m enable [M]usic\n"
880                                        " -p original [P]hysics (friction)\n"
881                                        " -s [S]ilent (no sound)\n");
882                                 exit(0);
883                         break;
884                         case 'm': // music
885                                 music_flag = 1;
886                         case 'p': // physics
887                                 friction = 1;
888                         break;
889                         case 's': // silent
890                                 sound_flag = 0;
891                                 music_flag = 0;
892                         break;
893                 }
894         }
895
896         if(init(fullscreen)) {
897                 printf ("ta: '%s'\n",initerror);
898                 return 1;
899         }
900
901         reset_rocks();
902         gameloop();
903
904         return 0;
905 }