Directories removed - projects have been moved into Fedora.
[fedora-mingw.git] / cairo / svgspacewar.c
1 // SVG Spacewar is copyright 2005 by Nigel Tao: nigel.tao@myrealbox.com
2 // Licenced under the GNU GPL.
3 // Developed on cairo version 0.4.0.
4 //
5 // 2005-03-31: Version 0.1.
6
7 #include <math.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <time.h>
11 #include <gdk/gdkkeysyms.h>
12 #include <gtk/gtk.h>
13 #include <sys/timeb.h>
14
15 #define WIDTH  800
16 #define HEIGHT 600
17
18 #define TWO_PI (2*M_PI)
19 #define PI     (M_PI)
20
21 // trig computations (and x, y, velocity, etc). are made in fixed point arithmetic
22 #define FIXED_POINT_SCALE_FACTOR 1024
23 #define FIXED_POINT_HALF_SCALE_FACTOR 32
24
25 // discretization of 360 degrees
26 #define NUMBER_OF_ROTATION_ANGLES 60
27 #define RADIANS_PER_ROTATION_ANGLE (TWO_PI / NUMBER_OF_ROTATION_ANGLES)
28
29 // equivalent to 25 fps
30 #define MILLIS_PER_FRAME 40
31
32 // a shot every 9/25 seconds = 8 ticks between shots
33 #define TICKS_BETWEEN_FIRE 8
34
35 // fudge this for bigger or smaller ships
36 #define GLOBAL_SHIP_SCALE_FACTOR 0.8
37
38 #define SHIP_ACCELERATION_FACTOR 1
39 #define SHIP_MAX_VELOCITY (10 * FIXED_POINT_SCALE_FACTOR)
40 #define SHIP_RADIUS ((int) (38 * FIXED_POINT_SCALE_FACTOR * GLOBAL_SHIP_SCALE_FACTOR))
41
42 #define SHIP_MAX_ENERGY 1000
43 #define DAMAGE_PER_MISSILE 200
44 #define ENERGY_PER_MISSILE 10
45
46 // bounce damage depends on how fast you're going
47 #define DAMAGE_PER_SHIP_BOUNCE_DIVISOR 3
48
49 #define NUMBER_OF_STARS 20
50
51 #define MAX_NUMBER_OF_MISSILES 60
52
53 #define MISSILE_RADIUS (4 * FIXED_POINT_SCALE_FACTOR)
54 #define MISSILE_SPEED 8
55 #define MISSILE_TICKS_TO_LIVE 60
56 #define MISSILE_EXPLOSION_TICKS_TO_LIVE 6
57
58 //------------------------------------------------------------------------------
59
60 typedef struct
61 {
62   gdouble r, g, b;
63 }
64 RGB_t;
65
66 typedef struct
67 {
68   int x, y;
69   int vx, vy;
70
71   // 0 is straight up, (NUMBER_OF_ROTATION_ANGLES / 4) is pointing right
72   int rotation;
73
74   // used for collision detection - we presume that an object is equivalent
75   // to its bounding circle, rather than trying to do something fancy.
76   int radius;
77 }
78 physics_t;
79
80 typedef struct
81 {
82   physics_t p;
83
84   gboolean is_thrusting;
85   gboolean is_turning_left;
86   gboolean is_turning_right;
87   gboolean is_firing;
88
89   RGB_t primary_color;
90   RGB_t secondary_color;
91
92   int ticks_until_can_fire;
93   int energy;
94
95   gboolean is_hit;
96   gboolean is_dead;
97 }
98 player_t;
99
100 typedef struct
101 {
102   gboolean is_alive;
103
104   physics_t p;
105
106   RGB_t primary_color;
107   RGB_t secondary_color;
108
109   int ticks_to_live;
110   gboolean has_exploded;
111 }
112 missile_t;
113
114 typedef struct
115 {
116   int x, y;
117   float rotation;
118   float scale;
119 }
120 star_t;
121
122 //------------------------------------------------------------------------------
123 // Forward definitions of functions
124
125 static void apply_physics (physics_t *);
126 static void apply_physics_to_player (player_t *);
127 static gboolean check_for_collision (physics_t *, physics_t *);
128 static void draw_energy_bar (cairo_t *, player_t *);
129 static void draw_flare (cairo_t *, RGB_t);
130 static void draw_missile (cairo_t *, missile_t *);
131 static void draw_exploded_missile (cairo_t *, missile_t *);
132 static void draw_ship_body (cairo_t *, player_t *);
133 static void draw_star (cairo_t * cr);
134 static void draw_turning_flare (cairo_t *, RGB_t, int);
135 static void enforce_minimum_distance (physics_t *, physics_t *);
136 static long get_time_millis (void);
137 static void init_missiles_array (void);
138 static void init_stars_array (void);
139 static void init_trigonometric_tables (void);
140 static void on_collision (player_t *, missile_t *);
141 static gint on_expose_event (GtkWidget *, GdkEventExpose *);
142 static gint on_key_event (GtkWidget *, GdkEventKey *, gboolean);
143 static gint on_key_press (GtkWidget *, GdkEventKey *);
144 static gint on_key_release (GtkWidget *, GdkEventKey *);
145 static gint on_timeout (gpointer);
146 static void reset ();
147 static void scale_for_aspect_ratio (cairo_t *, int, int);
148 static void show_text_message (cairo_t *, int, int, const char *);
149
150 //------------------------------------------------------------------------------
151
152 static player_t player1;
153 static player_t player2;
154
155 //------------------------------------------------------------------------------
156
157 static missile_t missiles[MAX_NUMBER_OF_MISSILES];
158 static int next_missile_index = 0;
159
160 static void
161 init_missiles_array ()
162 {
163   int i;
164
165   for (i = 0; i < MAX_NUMBER_OF_MISSILES; i++)
166     {
167       missiles[i].p.radius = MISSILE_RADIUS;
168       missiles[i].is_alive = FALSE;
169     }
170 }
171
172 //------------------------------------------------------------------------------
173
174 static star_t stars[NUMBER_OF_STARS];
175
176 #ifdef WIN32
177 // For Windows.
178 double drand48 ()
179 {
180   return (double) rand () / RAND_MAX;
181 }
182
183 int random ()
184 {
185   return rand () * 32768 + rand ();
186 }
187 #endif
188
189 static void
190 init_stars_array ()
191 {
192   int i;
193
194   for (i = 0; i < NUMBER_OF_STARS; i++)
195     {
196       stars[i].x = random () % WIDTH;
197       stars[i].y = random () % HEIGHT;
198       stars[i].rotation = drand48 () * TWO_PI;
199       stars[i].scale = 0.5 + (drand48 ());
200     }
201 }
202
203 //------------------------------------------------------------------------------
204
205 static gboolean show_fps = TRUE;
206 static int number_of_frames = 0;
207 static long millis_taken_for_frames = 0;
208 static float debug_scale_factor = 1.0f;
209 static const char *game_over_message = NULL;
210
211 //------------------------------------------------------------------------------
212
213 static int cos_table[NUMBER_OF_ROTATION_ANGLES];
214 static int sin_table[NUMBER_OF_ROTATION_ANGLES];
215
216 static void
217 init_trigonometric_tables ()
218 {
219   int i;
220   int q = (NUMBER_OF_ROTATION_ANGLES / 4);
221
222   for (i = 0; i < NUMBER_OF_ROTATION_ANGLES; i++)
223     {
224       // our angle system is "true north" - 0 is straight up, whereas
225       // cos & sin take 0 as east (and in radians).
226       double angle_in_radians = (q - i) * TWO_PI / NUMBER_OF_ROTATION_ANGLES;
227       cos_table[i] =
228         +(int) (cos (angle_in_radians) * FIXED_POINT_SCALE_FACTOR);
229
230       // also, our graphics system is "y axis down", although in regular math,
231       // the y axis is "up", so we have to multiply sin by -1.
232       sin_table[i] =
233         -(int) (sin (angle_in_radians) * FIXED_POINT_SCALE_FACTOR);
234     }
235 }
236
237 //------------------------------------------------------------------------------
238
239 gint
240 main (gint argc, gchar ** argv)
241 {
242   GtkWidget *window;
243
244   srand ((unsigned int) time (NULL));
245
246   init_trigonometric_tables ();
247   reset ();
248
249   gtk_init (&argc, &argv);
250
251   window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
252   g_signal_connect (G_OBJECT (window), "delete-event",
253                     G_CALLBACK (gtk_main_quit), NULL);
254
255   gtk_window_set_default_size (GTK_WINDOW (window), WIDTH, HEIGHT);
256
257   g_signal_connect (G_OBJECT (window), "expose_event",
258                     G_CALLBACK (on_expose_event), NULL);
259   g_signal_connect (G_OBJECT (window), "key_press_event",
260                     G_CALLBACK (on_key_press), NULL);
261   g_signal_connect (G_OBJECT (window), "key_release_event",
262                     G_CALLBACK (on_key_release), NULL);
263   g_timeout_add (MILLIS_PER_FRAME, (GSourceFunc) on_timeout, window);
264
265   gtk_widget_show_all (window);
266   gtk_main ();
267
268   return 0;
269 }
270
271 //------------------------------------------------------------------------------
272
273 static long
274 get_time_millis (void)
275 {
276   struct timeb tp;
277   ftime (&tp);
278   return (long) ((tp.time * 1000) + tp.millitm);
279 }
280
281 //------------------------------------------------------------------------------
282
283 static gint
284 on_expose_event (GtkWidget * widget, GdkEventExpose * event)
285 {
286   cairo_t *cr = gdk_cairo_create (widget->window);
287   int i;
288   long start_time = 0;
289   if (show_fps)
290     {
291       start_time = get_time_millis ();
292     }
293
294   cairo_save (cr);
295
296   scale_for_aspect_ratio (cr, widget->allocation.width,
297                           widget->allocation.height);
298
299   cairo_scale (cr, debug_scale_factor, debug_scale_factor);
300
301   /* draw background space color */
302   cairo_set_source_rgb (cr, 0.1, 0.0, 0.1);
303   cairo_paint (cr);
304
305   // draw any stars...
306   for (i = 0; i < NUMBER_OF_STARS; i++)
307     {
308       cairo_save (cr);
309       cairo_translate (cr, stars[i].x, stars[i].y);
310       cairo_rotate (cr, stars[i].rotation);
311       cairo_scale (cr, stars[i].scale, stars[i].scale);
312       draw_star (cr);
313       cairo_restore (cr);
314     }
315
316   // ... the energy bars...
317   cairo_save (cr);
318   cairo_translate (cr, 30, 30);
319   cairo_rotate (cr, 0);
320   draw_energy_bar (cr, &player1);
321   cairo_restore (cr);
322
323   cairo_save (cr);
324   cairo_translate (cr, WIDTH - 30, 30);
325   cairo_rotate (cr, PI);
326   draw_energy_bar (cr, &player2);
327   cairo_restore (cr);
328
329   // ... the two ships...
330   cairo_save (cr);
331   cairo_translate (cr, player1.p.x / FIXED_POINT_SCALE_FACTOR,
332                    player1.p.y / FIXED_POINT_SCALE_FACTOR);
333   cairo_rotate (cr, player1.p.rotation * RADIANS_PER_ROTATION_ANGLE);
334   draw_ship_body (cr, &player1);
335   cairo_restore (cr);
336
337   cairo_save (cr);
338   cairo_translate (cr, player2.p.x / FIXED_POINT_SCALE_FACTOR,
339                    player2.p.y / FIXED_POINT_SCALE_FACTOR);
340   cairo_rotate (cr, player2.p.rotation * RADIANS_PER_ROTATION_ANGLE);
341   draw_ship_body (cr, &player2);
342   cairo_restore (cr);
343
344   // ... and any missiles.
345   for (i = 0; i < MAX_NUMBER_OF_MISSILES; i++)
346     {
347       if (missiles[i].is_alive)
348         {
349           cairo_save (cr);
350           cairo_translate (cr, missiles[i].p.x / FIXED_POINT_SCALE_FACTOR,
351                            missiles[i].p.y / FIXED_POINT_SCALE_FACTOR);
352           cairo_rotate (cr,
353                         missiles[i].p.rotation * RADIANS_PER_ROTATION_ANGLE);
354           draw_missile (cr, &(missiles[i]));
355           cairo_restore (cr);
356         }
357     }
358
359   if (game_over_message == NULL)
360     {
361       if (player1.is_dead)
362         {
363           game_over_message = (player2.is_dead) ? "DRAW" : "RED wins";
364         }
365       else
366         {
367           game_over_message = (player2.is_dead) ? "BLUE wins" : NULL;
368         }
369     }
370   if (game_over_message != NULL)
371     {
372       show_text_message (cr, 80, -30, game_over_message);
373       show_text_message (cr, 30, +40, "Press [SPACE] to restart");
374     }
375
376   cairo_restore (cr);
377
378   if (show_fps)
379     {
380       number_of_frames++;
381       millis_taken_for_frames += get_time_millis () - start_time;
382       if (number_of_frames >= 100)
383         {
384           double fps =
385             1000.0 * ((double) number_of_frames) /
386             ((double) millis_taken_for_frames);
387           printf ("%d frames in %ldms (%.3ffps)\n", number_of_frames,
388                   millis_taken_for_frames, fps);
389           number_of_frames = 0;
390           millis_taken_for_frames = 0L;
391         }
392     }
393
394   cairo_destroy (cr);
395   return TRUE;
396 }
397
398 //------------------------------------------------------------------------------
399
400 static void
401 scale_for_aspect_ratio (cairo_t * cr, int widget_width, int widget_height)
402 {
403   double scale;
404   int playfield_width, playfield_height;
405   int tx, ty;
406   gboolean is_widget_wider;
407
408   is_widget_wider = (widget_width * HEIGHT) > (WIDTH * widget_height);
409
410   if (is_widget_wider)
411     {
412       scale = ((double) widget_height) / HEIGHT;
413       playfield_width = (WIDTH * widget_height) / HEIGHT;
414       playfield_height = widget_height;
415       tx = (widget_width - playfield_width) / 2;
416       ty = 0;
417     }
418   else
419     {
420       scale = ((double) widget_width) / WIDTH;
421       playfield_width = widget_width;
422       playfield_height = (HEIGHT * widget_width) / WIDTH;
423       tx = 0;
424       ty = (widget_height - playfield_height) / 2;
425     }
426
427   cairo_translate (cr, tx, ty);
428   cairo_rectangle (cr, 0, 0, playfield_width, playfield_height);
429   cairo_clip (cr);
430
431   cairo_scale (cr, scale, scale);
432 }
433
434 //------------------------------------------------------------------------------
435
436 static void
437 draw_energy_bar (cairo_t * cr, player_t * p)
438 {
439   cairo_pattern_t *pat;
440   double alpha = 0.6;
441
442   cairo_save (cr);
443
444   cairo_rectangle (cr, 0, -5, p->energy / 5, 10);
445
446   pat = cairo_pattern_create_linear (0, 0, SHIP_MAX_ENERGY / 5, 0);
447   cairo_pattern_add_color_stop_rgba (pat, 0,
448                                      p->secondary_color.r,
449                                      p->secondary_color.g,
450                                      p->secondary_color.b, alpha);
451   cairo_pattern_add_color_stop_rgba (pat, 1, p->primary_color.r,
452                                      p->primary_color.g, p->primary_color.b,
453                                      alpha);
454
455   cairo_set_source (cr, pat);
456   cairo_fill_preserve (cr);
457   cairo_pattern_destroy (pat);
458
459   cairo_set_source_rgb (cr, 0, 0, 0);
460   cairo_stroke (cr);
461   cairo_restore (cr);
462 }
463
464 //------------------------------------------------------------------------------
465
466 static void
467 draw_ship_body (cairo_t * cr, player_t * p)
468 {
469   cairo_pattern_t *pat;
470
471   if (p->is_hit)
472     {
473       cairo_set_source_rgba (cr, p->primary_color.r, p->primary_color.g,
474                              p->primary_color.b, 0.5);
475       cairo_arc (cr, 0, 0, SHIP_RADIUS / FIXED_POINT_SCALE_FACTOR, 0, TWO_PI);
476       cairo_stroke (cr);
477     }
478
479   cairo_save (cr);
480   cairo_scale (cr, GLOBAL_SHIP_SCALE_FACTOR, GLOBAL_SHIP_SCALE_FACTOR);
481
482   if (!p->is_dead)
483     {
484
485       if (p->is_thrusting)
486         {
487           draw_flare (cr, p->primary_color);
488         }
489
490       if (p->is_turning_left && !p->is_turning_right)
491         {
492           draw_turning_flare (cr, p->primary_color, -1.0);
493         }
494
495       if (!p->is_turning_left && p->is_turning_right)
496         {
497           draw_turning_flare (cr, p->primary_color, 1.0);
498         }
499     }
500
501   cairo_move_to (cr, 0, -33);
502   cairo_curve_to (cr, 2, -33, 3, -34, 4, -35);
503   cairo_curve_to (cr, 8, -10, 6, 15, 15, 15);
504   cairo_line_to (cr, 20, 15);
505   cairo_line_to (cr, 20, 7);
506   cairo_curve_to (cr, 25, 10, 28, 22, 25, 28);
507   cairo_curve_to (cr, 20, 26, 8, 24, 0, 24);
508   // half way point
509   cairo_curve_to (cr, -8, 24, -20, 26, -25, 28);
510   cairo_curve_to (cr, -28, 22, -25, 10, -20, 7);
511   cairo_line_to (cr, -20, 15);
512   cairo_line_to (cr, -15, 15);
513   cairo_curve_to (cr, -6, 15, -8, -10, -4, -35);
514   cairo_curve_to (cr, -3, -34, -2, -33, 0, -33);
515
516   pat = cairo_pattern_create_linear (-30.0, -30.0, 30.0, 30.0);
517   cairo_pattern_add_color_stop_rgba (pat, 0,
518                                      p->primary_color.r, p->primary_color.g,
519                                      p->primary_color.b, 1);
520   cairo_pattern_add_color_stop_rgba (pat, 1, p->secondary_color.r,
521                                      p->secondary_color.g,
522                                      p->secondary_color.b, 1);
523
524   cairo_set_source (cr, pat);
525   cairo_fill_preserve (cr);
526   cairo_pattern_destroy (pat);
527
528   cairo_set_source_rgb (cr, 0, 0, 0);
529   cairo_stroke (cr);
530   cairo_restore (cr);
531 }
532
533 //------------------------------------------------------------------------------
534
535 static void
536 draw_flare (cairo_t * cr, RGB_t color)
537 {
538   cairo_pattern_t *pat;
539
540   cairo_save (cr);
541   cairo_translate (cr, 0, 22);
542   pat = cairo_pattern_create_radial (0, 0, 2, 0, 5, 12);
543
544   cairo_pattern_add_color_stop_rgba (pat, 0.0, color.r, color.g, color.b, 1);
545   cairo_pattern_add_color_stop_rgba (pat, 0.3, 1, 1, 1, 1);
546   cairo_pattern_add_color_stop_rgba (pat, 1.0, color.r, color.g, color.b, 0);
547   cairo_set_source (cr, pat);
548   cairo_arc (cr, 0, 0, 20, 0, TWO_PI);
549   cairo_fill (cr);
550   cairo_pattern_destroy (pat);
551   cairo_restore (cr);
552 }
553
554 //------------------------------------------------------------------------------
555
556 static void
557 draw_turning_flare (cairo_t * cr, RGB_t color, int right_hand_side)
558 {
559   cairo_pattern_t *pat;
560   cairo_save (cr);
561
562   cairo_translate (cr, -23 * right_hand_side, 28);
563   pat = cairo_pattern_create_radial (0, 0, 1, 0, 0, 7);
564   cairo_pattern_add_color_stop_rgba (pat, 0.0, 1, 1, 1, 1);
565   cairo_pattern_add_color_stop_rgba (pat, 1.0, color.r, color.g, color.b, 0);
566   cairo_set_source (cr, pat);
567   cairo_arc (cr, 0, 0, 7, 0, TWO_PI);
568   cairo_fill (cr);
569   cairo_pattern_destroy (pat);
570
571   cairo_translate (cr, 42 * right_hand_side, -22);
572   pat = cairo_pattern_create_radial (0, 0, 1, 0, 0, 7);
573   cairo_pattern_add_color_stop_rgba (pat, 0.0, 1, 1, 1, 1);
574   cairo_pattern_add_color_stop_rgba (pat, 1.0, color.r, color.g, color.b, 0);
575   cairo_set_source (cr, pat);
576   cairo_arc (cr, 0, 0, 5, 0, TWO_PI);
577   cairo_fill (cr);
578   cairo_pattern_destroy (pat);
579
580   cairo_restore (cr);
581 }
582
583 //------------------------------------------------------------------------------
584
585 static void
586 draw_missile (cairo_t * cr, missile_t * m)
587 {
588   cairo_save (cr);
589   cairo_scale (cr, GLOBAL_SHIP_SCALE_FACTOR, GLOBAL_SHIP_SCALE_FACTOR);
590
591   if (m->has_exploded)
592     {
593       draw_exploded_missile (cr, m);
594     }
595   else
596     {
597       cairo_pattern_t *pat;
598
599       double alpha = ((double) m->ticks_to_live) / MISSILE_TICKS_TO_LIVE;
600       // non-linear scaling so things don't fade out too fast
601       alpha = 1.0 - (1.0 - alpha) * (1.0 - alpha);
602
603       cairo_save (cr);
604       cairo_move_to (cr, 0, -4);
605       cairo_curve_to (cr, 3, -4, 4, -2, 4, 0);
606       cairo_curve_to (cr, 4, 4, 2, 10, 0, 18);
607       // half way point
608       cairo_curve_to (cr, -2, 10, -4, 4, -4, 0);
609       cairo_curve_to (cr, -4, -2, -3, -4, 0, -4);
610
611       pat = cairo_pattern_create_linear (0.0, -5.0, 0.0, 5.0);
612       cairo_pattern_add_color_stop_rgba (pat, 0,
613                                          m->primary_color.r,
614                                          m->primary_color.g,
615                                          m->primary_color.b, alpha);
616       cairo_pattern_add_color_stop_rgba (pat, 1, m->secondary_color.r,
617                                          m->secondary_color.g,
618                                          m->secondary_color.b, alpha);
619
620       cairo_set_source (cr, pat);
621       cairo_fill (cr);
622       cairo_pattern_destroy (pat);
623       cairo_restore (cr);
624
625       cairo_save (cr);
626       cairo_arc (cr, 0, 0, 3, 0, TWO_PI);
627
628       pat = cairo_pattern_create_linear (0, 3, 0, -3);
629       cairo_pattern_add_color_stop_rgba (pat, 0,
630                                          m->primary_color.r,
631                                          m->primary_color.g,
632                                          m->primary_color.b, alpha);
633       cairo_pattern_add_color_stop_rgba (pat, 1, m->secondary_color.r,
634                                          m->secondary_color.g,
635                                          m->secondary_color.b, alpha);
636
637       cairo_set_source (cr, pat);
638       cairo_fill (cr);
639       cairo_pattern_destroy (pat);
640       cairo_restore (cr);
641     }
642
643   cairo_restore (cr);
644 }
645
646 //------------------------------------------------------------------------------
647
648 static void
649 draw_exploded_missile (cairo_t * cr, missile_t * m)
650 {
651   double alpha;
652   cairo_pattern_t *pat;
653
654   cairo_save (cr);
655   cairo_scale (cr, GLOBAL_SHIP_SCALE_FACTOR, GLOBAL_SHIP_SCALE_FACTOR);
656
657   alpha = ((double) m->ticks_to_live) / MISSILE_EXPLOSION_TICKS_TO_LIVE;
658   alpha = 1.0 - (1.0 - alpha) * (1.0 - alpha);
659
660   cairo_arc (cr, 0, 0, 30, 0, TWO_PI);
661
662   pat = cairo_pattern_create_radial (0, 0, 0, 0, 0, 30);
663   cairo_pattern_add_color_stop_rgba (pat, 0,
664                                      m->primary_color.r, m->primary_color.g,
665                                      m->primary_color.b, alpha);
666   cairo_pattern_add_color_stop_rgba (pat, 0.5, m->secondary_color.r,
667                                      m->secondary_color.g,
668                                      m->secondary_color.b, alpha * 0.75);
669   cairo_pattern_add_color_stop_rgba (pat, 1, 0, 0, 0, 0);
670
671   cairo_set_source (cr, pat);
672   cairo_fill (cr);
673   cairo_pattern_destroy (pat);
674   cairo_restore (cr);
675 }
676
677 //------------------------------------------------------------------------------
678
679 static void
680 draw_star (cairo_t * cr)
681 {
682   int a = NUMBER_OF_ROTATION_ANGLES / 10;
683   float r1 = 5.0;
684   float r2 = 2.0;
685   float c;
686   int i;
687
688   cairo_save (cr);
689   cairo_move_to (cr, r1 * cos_table[0] / FIXED_POINT_SCALE_FACTOR,
690                  r1 * sin_table[0] / FIXED_POINT_SCALE_FACTOR);
691
692   for (i = 0; i < 5; i++) {
693     cairo_line_to (cr, r1 * cos_table[0] / FIXED_POINT_SCALE_FACTOR,
694                    r1 * sin_table[0] / FIXED_POINT_SCALE_FACTOR);
695     cairo_line_to (cr, r2 * cos_table[a] / FIXED_POINT_SCALE_FACTOR,
696                    r2 * sin_table[a] / FIXED_POINT_SCALE_FACTOR);
697     cairo_rotate (cr, 4*a*PI/NUMBER_OF_ROTATION_ANGLES);
698   }
699
700   cairo_close_path (cr);
701   cairo_restore (cr);
702
703   c = 0.5;
704   cairo_set_source_rgb (cr, c, c, c);
705   cairo_fill (cr);
706 }
707
708 //------------------------------------------------------------------------------
709
710 static gint
711 on_timeout (gpointer data)
712 {
713   int i;
714
715   player1.is_hit = FALSE;
716   player2.is_hit = FALSE;
717
718   apply_physics_to_player (&player1);
719   apply_physics_to_player (&player2);
720
721   if (check_for_collision (&(player1.p), &(player2.p)))
722     {
723       int p1vx;
724       int p1vy;
725       int p2vx;
726       int p2vy;
727
728       int dvx;
729       int dvy;
730       int dv2;
731       int damage;
732
733       enforce_minimum_distance (&(player1.p), &(player2.p));
734
735       p1vx = player1.p.vx;
736       p1vy = player1.p.vy;
737       p2vx = player2.p.vx;
738       p2vy = player2.p.vy;
739
740       dvx = (p1vx - p2vx) / FIXED_POINT_HALF_SCALE_FACTOR;
741       dvy = (p1vy - p2vy) / FIXED_POINT_HALF_SCALE_FACTOR;
742       dv2 = (dvx * dvx) + (dvy * dvy);
743       damage = ((int)(sqrt (dv2))) / DAMAGE_PER_SHIP_BOUNCE_DIVISOR;
744
745       player1.energy -= damage;
746       player2.energy -= damage;
747       player1.is_hit = TRUE;
748       player2.is_hit = TRUE;
749
750       player1.p.vx = (p1vx * -2 / 8) + (p2vx * +5 / 8);
751       player1.p.vy = (p1vy * -2 / 8) + (p2vy * +5 / 8);
752       player2.p.vx = (p1vx * +5 / 8) + (p2vx * -2 / 8);
753       player2.p.vy = (p1vy * +5 / 8) + (p2vy * -2 / 8);
754     }
755
756   for (i = 0; i < MAX_NUMBER_OF_MISSILES; i++)
757     {
758       if (missiles[i].is_alive)
759         {
760           apply_physics (&(missiles[i].p));
761
762           if (!missiles[i].has_exploded)
763             {
764               if (check_for_collision (&(missiles[i].p), &(player1.p)))
765                 {
766                   on_collision (&player1, &(missiles[i]));
767                 }
768
769               if (check_for_collision (&(missiles[i].p), &(player2.p)))
770                 {
771                   on_collision (&player2, &(missiles[i]));
772                 }
773             }
774
775           missiles[i].ticks_to_live--;
776           if (missiles[i].ticks_to_live <= 0)
777             {
778               missiles[i].is_alive = FALSE;
779             }
780         }
781     }
782
783   if (player1.energy <= 0)
784     {
785       player1.energy = 0;
786       player1.is_dead = TRUE;
787     }
788   else
789     {
790       player1.energy = MIN (SHIP_MAX_ENERGY, player1.energy + 1);
791     }
792
793   if (player2.energy <= 0)
794     {
795       player2.energy = 0;
796       player2.is_dead = TRUE;
797     }
798   else
799     {
800       player2.energy = MIN (SHIP_MAX_ENERGY, player2.energy + 1);
801     }
802
803   gtk_widget_queue_draw ((GtkWidget *) data);
804   return TRUE;
805 }
806
807 //------------------------------------------------------------------------------
808
809 static void
810 apply_physics_to_player (player_t * player)
811 {
812   int v2, m2;
813   physics_t *p = &(player->p);
814
815   if (!player->is_dead)
816     {
817       // check if player is turning left, ...
818       if (player->is_turning_left)
819         {
820           p->rotation--;
821           while (p->rotation < 0)
822             {
823               p->rotation += NUMBER_OF_ROTATION_ANGLES;
824             }
825         }
826
827       // ... or right.
828       if (player->is_turning_right)
829         {
830           p->rotation++;
831           while (p->rotation >= NUMBER_OF_ROTATION_ANGLES)
832             {
833               p->rotation -= NUMBER_OF_ROTATION_ANGLES;
834             }
835         }
836
837       // check if accelerating
838       if (player->is_thrusting)
839         {
840           p->vx += SHIP_ACCELERATION_FACTOR * cos_table[p->rotation];
841           p->vy += SHIP_ACCELERATION_FACTOR * sin_table[p->rotation];
842         }
843
844       // apply velocity upper bound
845       v2 = ((p->vx) * (p->vx)) + ((p->vy) * (p->vy));
846       m2 = SHIP_MAX_VELOCITY * SHIP_MAX_VELOCITY;
847       if (v2 > m2)
848         {
849           p->vx = (int) (((double) (p->vx) * m2) / v2);
850           p->vy = (int) (((double) (p->vy) * m2) / v2);
851         }
852
853       // check if player is shooting
854       if (player->ticks_until_can_fire == 0)
855         {
856           if ((player->is_firing) && (player->energy > ENERGY_PER_MISSILE))
857             {
858               int xx = cos_table[p->rotation];
859               int yy = sin_table[p->rotation];
860
861               missile_t *m = &(missiles[next_missile_index++]);
862
863               player->energy -= ENERGY_PER_MISSILE;
864
865               if (next_missile_index == MAX_NUMBER_OF_MISSILES)
866                 {
867                   next_missile_index = 0;
868                 }
869
870               m->p.x =
871                 p->x +
872                 (((SHIP_RADIUS +
873                    MISSILE_RADIUS) / FIXED_POINT_SCALE_FACTOR) * xx);
874               m->p.y =
875                 p->y +
876                 (((SHIP_RADIUS +
877                    MISSILE_RADIUS) / FIXED_POINT_SCALE_FACTOR) * yy);
878               m->p.vx = p->vx + (MISSILE_SPEED * xx);
879               m->p.vy = p->vy + (MISSILE_SPEED * yy);
880               m->p.rotation = p->rotation;
881               m->ticks_to_live = MISSILE_TICKS_TO_LIVE;
882               m->primary_color = player->primary_color;
883               m->secondary_color = player->secondary_color;
884               m->is_alive = TRUE;
885               m->has_exploded = FALSE;
886
887               player->ticks_until_can_fire += TICKS_BETWEEN_FIRE;
888             }
889         }
890       else
891         {
892           player->ticks_until_can_fire--;
893         }
894     }
895
896   // apply velocity deltas to displacement  
897   apply_physics (p);
898 }
899
900 //------------------------------------------------------------------------------
901
902 static void
903 apply_physics (physics_t * p)
904 {
905   p->x += p->vx;
906   while (p->x > (WIDTH * FIXED_POINT_SCALE_FACTOR))
907     {
908       p->x -= (WIDTH * FIXED_POINT_SCALE_FACTOR);
909     }
910   while (p->x < 0)
911     {
912       p->x += (WIDTH * FIXED_POINT_SCALE_FACTOR);
913     }
914
915   p->y += p->vy;
916   while (p->y > (HEIGHT * FIXED_POINT_SCALE_FACTOR))
917     {
918       p->y -= (HEIGHT * FIXED_POINT_SCALE_FACTOR);
919     }
920   while (p->y < 0)
921     {
922       p->y += (HEIGHT * FIXED_POINT_SCALE_FACTOR);
923     }
924 }
925
926 //------------------------------------------------------------------------------
927
928 static gboolean
929 check_for_collision (physics_t * p1, physics_t * p2)
930 {
931   int dx = (p1->x - p2->x) / FIXED_POINT_HALF_SCALE_FACTOR;
932   int dy = (p1->y - p2->y) / FIXED_POINT_HALF_SCALE_FACTOR;
933   int r = (p1->radius + p2->radius) / FIXED_POINT_HALF_SCALE_FACTOR;
934   int d2 = (dx * dx) + (dy * dy);
935   return (d2 < (r * r)) ? TRUE : FALSE;
936 }
937
938 //------------------------------------------------------------------------------
939
940 static void
941 enforce_minimum_distance (physics_t * p1, physics_t * p2)
942 {
943   int dx = p1->x - p2->x;
944   int dy = p1->y - p2->y;
945   double d2 = (((double) dx) * dx) + (((double) dy) * dy);
946   int d = (int) sqrt (d2);
947
948   int r = p1->radius + p2->radius;
949
950   // normalize dx and dy to length = ((r - d) / 2) + fudge_factor
951   int desired_vector_length = ((r - d) * 5) / 8;
952
953   dx *= desired_vector_length;
954   dy *= desired_vector_length;
955   dx /= d;
956   dy /= d;
957
958   p1->x += dx;
959   p1->y += dy;
960   p2->x -= dx;
961   p2->y -= dy;
962 }
963
964 //------------------------------------------------------------------------------
965
966 static void
967 on_collision (player_t * p, missile_t * m)
968 {
969   p->energy -= DAMAGE_PER_MISSILE;
970   p->is_hit = TRUE;
971   m->has_exploded = TRUE;
972   m->ticks_to_live = MISSILE_EXPLOSION_TICKS_TO_LIVE;
973   m->p.vx = 0;
974   m->p.vy = 0;
975 }
976
977 //------------------------------------------------------------------------------
978
979 static void
980 show_text_message (cairo_t * cr, int font_size, int dy, const char *message)
981 {
982   double x, y;
983   cairo_text_extents_t extents;
984
985   cairo_save (cr);
986
987   cairo_select_font_face (cr, "Serif",
988                           CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
989
990   cairo_set_font_size (cr, font_size);
991   cairo_text_extents (cr, message, &extents);
992   x = (WIDTH / 2) - (extents.width / 2 + extents.x_bearing);
993   y = (HEIGHT / 2) - (extents.height / 2 + extents.y_bearing);
994
995   cairo_set_source_rgba (cr, 1, 1, 1, 1);
996   cairo_move_to (cr, x, y + dy);
997   cairo_show_text (cr, message);
998   cairo_restore (cr);
999 }
1000
1001 //------------------------------------------------------------------------------
1002
1003 static void
1004 reset ()
1005 {
1006   player1.p.x = 200 * FIXED_POINT_SCALE_FACTOR;
1007   player1.p.y = 200 * FIXED_POINT_SCALE_FACTOR;
1008   player1.p.vx = 0;
1009   player1.p.vy = 0;
1010   player1.p.rotation = random () % NUMBER_OF_ROTATION_ANGLES;
1011   player1.p.radius = SHIP_RADIUS;
1012   player1.is_thrusting = FALSE;
1013   player1.is_turning_left = FALSE;
1014   player1.is_turning_right = FALSE;
1015   player1.is_firing = FALSE;
1016   player1.primary_color.r = 0.3;
1017   player1.primary_color.g = 0.5;
1018   player1.primary_color.b = 0.9;
1019   player1.secondary_color.r = 0.1;
1020   player1.secondary_color.g = 0.3;
1021   player1.secondary_color.b = 0.3;
1022   player1.ticks_until_can_fire = 0;
1023   player1.energy = SHIP_MAX_ENERGY;
1024   player1.is_hit = FALSE;
1025   player1.is_dead = FALSE;
1026
1027   player2.p.x = 600 * FIXED_POINT_SCALE_FACTOR;
1028   player2.p.y = 400 * FIXED_POINT_SCALE_FACTOR;
1029   player2.p.vx = 0;
1030   player2.p.vy = 0;
1031   player2.p.rotation = random () % NUMBER_OF_ROTATION_ANGLES;
1032   player2.p.radius = SHIP_RADIUS;
1033   player2.is_thrusting = FALSE;
1034   player2.is_turning_left = FALSE;
1035   player2.is_turning_right = FALSE;
1036   player2.is_firing = FALSE;
1037   player2.primary_color.r = 0.9;
1038   player2.primary_color.g = 0.2;
1039   player2.primary_color.b = 0.3;
1040   player2.secondary_color.r = 0.5;
1041   player2.secondary_color.g = 0.2;
1042   player2.secondary_color.b = 0.3;
1043   player2.ticks_until_can_fire = 0;
1044   player2.energy = SHIP_MAX_ENERGY;
1045   player2.is_hit = FALSE;
1046   player2.is_dead = FALSE;
1047
1048   init_stars_array ();
1049   init_missiles_array ();
1050
1051   game_over_message = NULL;
1052 }
1053
1054 //------------------------------------------------------------------------------
1055
1056 static gint
1057 on_key_press (GtkWidget * widget, GdkEventKey * event)
1058 {
1059   return on_key_event (widget, event, TRUE);
1060 }
1061
1062 //------------------------------------------------------------------------------
1063
1064 static gint
1065 on_key_release (GtkWidget * widget, GdkEventKey * event)
1066 {
1067   return on_key_event (widget, event, FALSE);
1068 }
1069
1070 //------------------------------------------------------------------------------
1071
1072 static gint
1073 on_key_event (GtkWidget * widget, GdkEventKey * event, gboolean key_is_on)
1074 {
1075   switch (event->keyval)
1076     {
1077     case GDK_Escape:
1078       gtk_main_quit ();
1079       break;
1080
1081     case GDK_bracketleft:
1082       if (key_is_on)
1083         {
1084           debug_scale_factor /= 1.25f;
1085           printf ("Scale: %f\n", debug_scale_factor);
1086         }
1087       break;
1088     case GDK_bracketright:
1089       if (key_is_on)
1090         {
1091           debug_scale_factor *= 1.25f;
1092           printf ("Scale: %f\n", debug_scale_factor);
1093         }
1094       break;
1095
1096     case GDK_space:
1097       if (game_over_message != NULL)
1098         {
1099           reset ();
1100         }
1101       break;
1102
1103     case GDK_a:
1104       player1.is_turning_left = key_is_on;
1105       break;
1106     case GDK_d:
1107       player1.is_turning_right = key_is_on;
1108       break;
1109     case GDK_w:
1110       player1.is_thrusting = key_is_on;
1111       break;
1112     case GDK_Control_L:
1113       player1.is_firing = key_is_on;
1114       break;
1115
1116     case GDK_Left:
1117     case GDK_KP_Left:
1118       player2.is_turning_left = key_is_on;
1119       break;
1120     case GDK_Right:
1121     case GDK_KP_Right:
1122       player2.is_turning_right = key_is_on;
1123       break;
1124     case GDK_Up:
1125     case GDK_KP_Up:
1126       player2.is_thrusting = key_is_on;
1127       break;
1128     case GDK_Control_R:
1129     case GDK_KP_Insert:
1130       player2.is_firing = key_is_on;
1131       break;
1132     }
1133   return TRUE;
1134 }