--- /dev/null
+// SVG Spacewar is copyright 2005 by Nigel Tao: nigel.tao@myrealbox.com
+// Licenced under the GNU GPL.
+// Developed on cairo version 0.4.0.
+//
+// 2005-03-31: Version 0.1.
+
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+#include <gdk/gdkkeysyms.h>
+#include <gtk/gtk.h>
+#include <sys/timeb.h>
+
+#define WIDTH 800
+#define HEIGHT 600
+
+#define TWO_PI (2*M_PI)
+#define PI (M_PI)
+
+// trig computations (and x, y, velocity, etc). are made in fixed point arithmetic
+#define FIXED_POINT_SCALE_FACTOR 1024
+#define FIXED_POINT_HALF_SCALE_FACTOR 32
+
+// discretization of 360 degrees
+#define NUMBER_OF_ROTATION_ANGLES 60
+#define RADIANS_PER_ROTATION_ANGLE (TWO_PI / NUMBER_OF_ROTATION_ANGLES)
+
+// equivalent to 25 fps
+#define MILLIS_PER_FRAME 40
+
+// a shot every 9/25 seconds = 8 ticks between shots
+#define TICKS_BETWEEN_FIRE 8
+
+// fudge this for bigger or smaller ships
+#define GLOBAL_SHIP_SCALE_FACTOR 0.8
+
+#define SHIP_ACCELERATION_FACTOR 1
+#define SHIP_MAX_VELOCITY (10 * FIXED_POINT_SCALE_FACTOR)
+#define SHIP_RADIUS ((int) (38 * FIXED_POINT_SCALE_FACTOR * GLOBAL_SHIP_SCALE_FACTOR))
+
+#define SHIP_MAX_ENERGY 1000
+#define DAMAGE_PER_MISSILE 200
+#define ENERGY_PER_MISSILE 10
+
+// bounce damage depends on how fast you're going
+#define DAMAGE_PER_SHIP_BOUNCE_DIVISOR 3
+
+#define NUMBER_OF_STARS 20
+
+#define MAX_NUMBER_OF_MISSILES 60
+
+#define MISSILE_RADIUS (4 * FIXED_POINT_SCALE_FACTOR)
+#define MISSILE_SPEED 8
+#define MISSILE_TICKS_TO_LIVE 60
+#define MISSILE_EXPLOSION_TICKS_TO_LIVE 6
+
+//------------------------------------------------------------------------------
+
+typedef struct
+{
+ gdouble r, g, b;
+}
+RGB_t;
+
+typedef struct
+{
+ int x, y;
+ int vx, vy;
+
+ // 0 is straight up, (NUMBER_OF_ROTATION_ANGLES / 4) is pointing right
+ int rotation;
+
+ // used for collision detection - we presume that an object is equivalent
+ // to its bounding circle, rather than trying to do something fancy.
+ int radius;
+}
+physics_t;
+
+typedef struct
+{
+ physics_t p;
+
+ gboolean is_thrusting;
+ gboolean is_turning_left;
+ gboolean is_turning_right;
+ gboolean is_firing;
+
+ RGB_t primary_color;
+ RGB_t secondary_color;
+
+ int ticks_until_can_fire;
+ int energy;
+
+ gboolean is_hit;
+ gboolean is_dead;
+}
+player_t;
+
+typedef struct
+{
+ gboolean is_alive;
+
+ physics_t p;
+
+ RGB_t primary_color;
+ RGB_t secondary_color;
+
+ int ticks_to_live;
+ gboolean has_exploded;
+}
+missile_t;
+
+typedef struct
+{
+ int x, y;
+ float rotation;
+ float scale;
+}
+star_t;
+
+//------------------------------------------------------------------------------
+// Forward definitions of functions
+
+static void apply_physics (physics_t *);
+static void apply_physics_to_player (player_t *);
+static gboolean check_for_collision (physics_t *, physics_t *);
+static void draw_energy_bar (cairo_t *, player_t *);
+static void draw_flare (cairo_t *, RGB_t);
+static void draw_missile (cairo_t *, missile_t *);
+static void draw_exploded_missile (cairo_t *, missile_t *);
+static void draw_ship_body (cairo_t *, player_t *);
+static void draw_star (cairo_t * cr);
+static void draw_turning_flare (cairo_t *, RGB_t, int);
+static void enforce_minimum_distance (physics_t *, physics_t *);
+static long get_time_millis (void);
+static void init_missiles_array (void);
+static void init_stars_array (void);
+static void init_trigonometric_tables (void);
+static void on_collision (player_t *, missile_t *);
+static gint on_expose_event (GtkWidget *, GdkEventExpose *);
+static gint on_key_event (GtkWidget *, GdkEventKey *, gboolean);
+static gint on_key_press (GtkWidget *, GdkEventKey *);
+static gint on_key_release (GtkWidget *, GdkEventKey *);
+static gint on_timeout (gpointer);
+static void reset ();
+static void scale_for_aspect_ratio (cairo_t *, int, int);
+static void show_text_message (cairo_t *, int, int, const char *);
+
+//------------------------------------------------------------------------------
+
+static player_t player1;
+static player_t player2;
+
+//------------------------------------------------------------------------------
+
+static missile_t missiles[MAX_NUMBER_OF_MISSILES];
+static int next_missile_index = 0;
+
+static void
+init_missiles_array ()
+{
+ int i;
+
+ for (i = 0; i < MAX_NUMBER_OF_MISSILES; i++)
+ {
+ missiles[i].p.radius = MISSILE_RADIUS;
+ missiles[i].is_alive = FALSE;
+ }
+}
+
+//------------------------------------------------------------------------------
+
+static star_t stars[NUMBER_OF_STARS];
+
+#ifdef WIN32
+// For Windows.
+double drand48 ()
+{
+ return (double) rand () / RAND_MAX;
+}
+
+int random ()
+{
+ return rand () * 32768 + rand ();
+}
+#endif
+
+static void
+init_stars_array ()
+{
+ int i;
+
+ for (i = 0; i < NUMBER_OF_STARS; i++)
+ {
+ stars[i].x = random () % WIDTH;
+ stars[i].y = random () % HEIGHT;
+ stars[i].rotation = drand48 () * TWO_PI;
+ stars[i].scale = 0.5 + (drand48 ());
+ }
+}
+
+//------------------------------------------------------------------------------
+
+static gboolean show_fps = TRUE;
+static int number_of_frames = 0;
+static long millis_taken_for_frames = 0;
+static float debug_scale_factor = 1.0f;
+static const char *game_over_message = NULL;
+
+//------------------------------------------------------------------------------
+
+static int cos_table[NUMBER_OF_ROTATION_ANGLES];
+static int sin_table[NUMBER_OF_ROTATION_ANGLES];
+
+static void
+init_trigonometric_tables ()
+{
+ int i;
+ int q = (NUMBER_OF_ROTATION_ANGLES / 4);
+
+ for (i = 0; i < NUMBER_OF_ROTATION_ANGLES; i++)
+ {
+ // our angle system is "true north" - 0 is straight up, whereas
+ // cos & sin take 0 as east (and in radians).
+ double angle_in_radians = (q - i) * TWO_PI / NUMBER_OF_ROTATION_ANGLES;
+ cos_table[i] =
+ +(int) (cos (angle_in_radians) * FIXED_POINT_SCALE_FACTOR);
+
+ // also, our graphics system is "y axis down", although in regular math,
+ // the y axis is "up", so we have to multiply sin by -1.
+ sin_table[i] =
+ -(int) (sin (angle_in_radians) * FIXED_POINT_SCALE_FACTOR);
+ }
+}
+
+//------------------------------------------------------------------------------
+
+gint
+main (gint argc, gchar ** argv)
+{
+ GtkWidget *window;
+
+ srand ((unsigned int) time (NULL));
+
+ init_trigonometric_tables ();
+ reset ();
+
+ gtk_init (&argc, &argv);
+
+ window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+ g_signal_connect (G_OBJECT (window), "delete-event",
+ G_CALLBACK (gtk_main_quit), NULL);
+
+ gtk_window_set_default_size (GTK_WINDOW (window), WIDTH, HEIGHT);
+
+ g_signal_connect (G_OBJECT (window), "expose_event",
+ G_CALLBACK (on_expose_event), NULL);
+ g_signal_connect (G_OBJECT (window), "key_press_event",
+ G_CALLBACK (on_key_press), NULL);
+ g_signal_connect (G_OBJECT (window), "key_release_event",
+ G_CALLBACK (on_key_release), NULL);
+ g_timeout_add (MILLIS_PER_FRAME, (GSourceFunc) on_timeout, window);
+
+ gtk_widget_show_all (window);
+ gtk_main ();
+
+ return 0;
+}
+
+//------------------------------------------------------------------------------
+
+static long
+get_time_millis (void)
+{
+ struct timeb tp;
+ ftime (&tp);
+ return (long) ((tp.time * 1000) + tp.millitm);
+}
+
+//------------------------------------------------------------------------------
+
+static gint
+on_expose_event (GtkWidget * widget, GdkEventExpose * event)
+{
+ cairo_t *cr = gdk_cairo_create (widget->window);
+ int i;
+ long start_time = 0;
+ if (show_fps)
+ {
+ start_time = get_time_millis ();
+ }
+
+ cairo_save (cr);
+
+ scale_for_aspect_ratio (cr, widget->allocation.width,
+ widget->allocation.height);
+
+ cairo_scale (cr, debug_scale_factor, debug_scale_factor);
+
+ /* draw background space color */
+ cairo_set_source_rgb (cr, 0.1, 0.0, 0.1);
+ cairo_paint (cr);
+
+ // draw any stars...
+ for (i = 0; i < NUMBER_OF_STARS; i++)
+ {
+ cairo_save (cr);
+ cairo_translate (cr, stars[i].x, stars[i].y);
+ cairo_rotate (cr, stars[i].rotation);
+ cairo_scale (cr, stars[i].scale, stars[i].scale);
+ draw_star (cr);
+ cairo_restore (cr);
+ }
+
+ // ... the energy bars...
+ cairo_save (cr);
+ cairo_translate (cr, 30, 30);
+ cairo_rotate (cr, 0);
+ draw_energy_bar (cr, &player1);
+ cairo_restore (cr);
+
+ cairo_save (cr);
+ cairo_translate (cr, WIDTH - 30, 30);
+ cairo_rotate (cr, PI);
+ draw_energy_bar (cr, &player2);
+ cairo_restore (cr);
+
+ // ... the two ships...
+ cairo_save (cr);
+ cairo_translate (cr, player1.p.x / FIXED_POINT_SCALE_FACTOR,
+ player1.p.y / FIXED_POINT_SCALE_FACTOR);
+ cairo_rotate (cr, player1.p.rotation * RADIANS_PER_ROTATION_ANGLE);
+ draw_ship_body (cr, &player1);
+ cairo_restore (cr);
+
+ cairo_save (cr);
+ cairo_translate (cr, player2.p.x / FIXED_POINT_SCALE_FACTOR,
+ player2.p.y / FIXED_POINT_SCALE_FACTOR);
+ cairo_rotate (cr, player2.p.rotation * RADIANS_PER_ROTATION_ANGLE);
+ draw_ship_body (cr, &player2);
+ cairo_restore (cr);
+
+ // ... and any missiles.
+ for (i = 0; i < MAX_NUMBER_OF_MISSILES; i++)
+ {
+ if (missiles[i].is_alive)
+ {
+ cairo_save (cr);
+ cairo_translate (cr, missiles[i].p.x / FIXED_POINT_SCALE_FACTOR,
+ missiles[i].p.y / FIXED_POINT_SCALE_FACTOR);
+ cairo_rotate (cr,
+ missiles[i].p.rotation * RADIANS_PER_ROTATION_ANGLE);
+ draw_missile (cr, &(missiles[i]));
+ cairo_restore (cr);
+ }
+ }
+
+ if (game_over_message == NULL)
+ {
+ if (player1.is_dead)
+ {
+ game_over_message = (player2.is_dead) ? "DRAW" : "RED wins";
+ }
+ else
+ {
+ game_over_message = (player2.is_dead) ? "BLUE wins" : NULL;
+ }
+ }
+ if (game_over_message != NULL)
+ {
+ show_text_message (cr, 80, -30, game_over_message);
+ show_text_message (cr, 30, +40, "Press [SPACE] to restart");
+ }
+
+ cairo_restore (cr);
+
+ if (show_fps)
+ {
+ number_of_frames++;
+ millis_taken_for_frames += get_time_millis () - start_time;
+ if (number_of_frames >= 100)
+ {
+ double fps =
+ 1000.0 * ((double) number_of_frames) /
+ ((double) millis_taken_for_frames);
+ printf ("%d frames in %ldms (%.3ffps)\n", number_of_frames,
+ millis_taken_for_frames, fps);
+ number_of_frames = 0;
+ millis_taken_for_frames = 0L;
+ }
+ }
+
+ cairo_destroy (cr);
+ return TRUE;
+}
+
+//------------------------------------------------------------------------------
+
+static void
+scale_for_aspect_ratio (cairo_t * cr, int widget_width, int widget_height)
+{
+ double scale;
+ int playfield_width, playfield_height;
+ int tx, ty;
+ gboolean is_widget_wider;
+
+ is_widget_wider = (widget_width * HEIGHT) > (WIDTH * widget_height);
+
+ if (is_widget_wider)
+ {
+ scale = ((double) widget_height) / HEIGHT;
+ playfield_width = (WIDTH * widget_height) / HEIGHT;
+ playfield_height = widget_height;
+ tx = (widget_width - playfield_width) / 2;
+ ty = 0;
+ }
+ else
+ {
+ scale = ((double) widget_width) / WIDTH;
+ playfield_width = widget_width;
+ playfield_height = (HEIGHT * widget_width) / WIDTH;
+ tx = 0;
+ ty = (widget_height - playfield_height) / 2;
+ }
+
+ cairo_translate (cr, tx, ty);
+ cairo_rectangle (cr, 0, 0, playfield_width, playfield_height);
+ cairo_clip (cr);
+
+ cairo_scale (cr, scale, scale);
+}
+
+//------------------------------------------------------------------------------
+
+static void
+draw_energy_bar (cairo_t * cr, player_t * p)
+{
+ cairo_pattern_t *pat;
+ double alpha = 0.6;
+
+ cairo_save (cr);
+
+ cairo_rectangle (cr, 0, -5, p->energy / 5, 10);
+
+ pat = cairo_pattern_create_linear (0, 0, SHIP_MAX_ENERGY / 5, 0);
+ cairo_pattern_add_color_stop_rgba (pat, 0,
+ p->secondary_color.r,
+ p->secondary_color.g,
+ p->secondary_color.b, alpha);
+ cairo_pattern_add_color_stop_rgba (pat, 1, p->primary_color.r,
+ p->primary_color.g, p->primary_color.b,
+ alpha);
+
+ cairo_set_source (cr, pat);
+ cairo_fill_preserve (cr);
+ cairo_pattern_destroy (pat);
+
+ cairo_set_source_rgb (cr, 0, 0, 0);
+ cairo_stroke (cr);
+ cairo_restore (cr);
+}
+
+//------------------------------------------------------------------------------
+
+static void
+draw_ship_body (cairo_t * cr, player_t * p)
+{
+ cairo_pattern_t *pat;
+
+ if (p->is_hit)
+ {
+ cairo_set_source_rgba (cr, p->primary_color.r, p->primary_color.g,
+ p->primary_color.b, 0.5);
+ cairo_arc (cr, 0, 0, SHIP_RADIUS / FIXED_POINT_SCALE_FACTOR, 0, TWO_PI);
+ cairo_stroke (cr);
+ }
+
+ cairo_save (cr);
+ cairo_scale (cr, GLOBAL_SHIP_SCALE_FACTOR, GLOBAL_SHIP_SCALE_FACTOR);
+
+ if (!p->is_dead)
+ {
+
+ if (p->is_thrusting)
+ {
+ draw_flare (cr, p->primary_color);
+ }
+
+ if (p->is_turning_left && !p->is_turning_right)
+ {
+ draw_turning_flare (cr, p->primary_color, -1.0);
+ }
+
+ if (!p->is_turning_left && p->is_turning_right)
+ {
+ draw_turning_flare (cr, p->primary_color, 1.0);
+ }
+ }
+
+ cairo_move_to (cr, 0, -33);
+ cairo_curve_to (cr, 2, -33, 3, -34, 4, -35);
+ cairo_curve_to (cr, 8, -10, 6, 15, 15, 15);
+ cairo_line_to (cr, 20, 15);
+ cairo_line_to (cr, 20, 7);
+ cairo_curve_to (cr, 25, 10, 28, 22, 25, 28);
+ cairo_curve_to (cr, 20, 26, 8, 24, 0, 24);
+ // half way point
+ cairo_curve_to (cr, -8, 24, -20, 26, -25, 28);
+ cairo_curve_to (cr, -28, 22, -25, 10, -20, 7);
+ cairo_line_to (cr, -20, 15);
+ cairo_line_to (cr, -15, 15);
+ cairo_curve_to (cr, -6, 15, -8, -10, -4, -35);
+ cairo_curve_to (cr, -3, -34, -2, -33, 0, -33);
+
+ pat = cairo_pattern_create_linear (-30.0, -30.0, 30.0, 30.0);
+ cairo_pattern_add_color_stop_rgba (pat, 0,
+ p->primary_color.r, p->primary_color.g,
+ p->primary_color.b, 1);
+ cairo_pattern_add_color_stop_rgba (pat, 1, p->secondary_color.r,
+ p->secondary_color.g,
+ p->secondary_color.b, 1);
+
+ cairo_set_source (cr, pat);
+ cairo_fill_preserve (cr);
+ cairo_pattern_destroy (pat);
+
+ cairo_set_source_rgb (cr, 0, 0, 0);
+ cairo_stroke (cr);
+ cairo_restore (cr);
+}
+
+//------------------------------------------------------------------------------
+
+static void
+draw_flare (cairo_t * cr, RGB_t color)
+{
+ cairo_pattern_t *pat;
+
+ cairo_save (cr);
+ cairo_translate (cr, 0, 22);
+ pat = cairo_pattern_create_radial (0, 0, 2, 0, 5, 12);
+
+ cairo_pattern_add_color_stop_rgba (pat, 0.0, color.r, color.g, color.b, 1);
+ cairo_pattern_add_color_stop_rgba (pat, 0.3, 1, 1, 1, 1);
+ cairo_pattern_add_color_stop_rgba (pat, 1.0, color.r, color.g, color.b, 0);
+ cairo_set_source (cr, pat);
+ cairo_arc (cr, 0, 0, 20, 0, TWO_PI);
+ cairo_fill (cr);
+ cairo_pattern_destroy (pat);
+ cairo_restore (cr);
+}
+
+//------------------------------------------------------------------------------
+
+static void
+draw_turning_flare (cairo_t * cr, RGB_t color, int right_hand_side)
+{
+ cairo_pattern_t *pat;
+ cairo_save (cr);
+
+ cairo_translate (cr, -23 * right_hand_side, 28);
+ pat = cairo_pattern_create_radial (0, 0, 1, 0, 0, 7);
+ cairo_pattern_add_color_stop_rgba (pat, 0.0, 1, 1, 1, 1);
+ cairo_pattern_add_color_stop_rgba (pat, 1.0, color.r, color.g, color.b, 0);
+ cairo_set_source (cr, pat);
+ cairo_arc (cr, 0, 0, 7, 0, TWO_PI);
+ cairo_fill (cr);
+ cairo_pattern_destroy (pat);
+
+ cairo_translate (cr, 42 * right_hand_side, -22);
+ pat = cairo_pattern_create_radial (0, 0, 1, 0, 0, 7);
+ cairo_pattern_add_color_stop_rgba (pat, 0.0, 1, 1, 1, 1);
+ cairo_pattern_add_color_stop_rgba (pat, 1.0, color.r, color.g, color.b, 0);
+ cairo_set_source (cr, pat);
+ cairo_arc (cr, 0, 0, 5, 0, TWO_PI);
+ cairo_fill (cr);
+ cairo_pattern_destroy (pat);
+
+ cairo_restore (cr);
+}
+
+//------------------------------------------------------------------------------
+
+static void
+draw_missile (cairo_t * cr, missile_t * m)
+{
+ cairo_save (cr);
+ cairo_scale (cr, GLOBAL_SHIP_SCALE_FACTOR, GLOBAL_SHIP_SCALE_FACTOR);
+
+ if (m->has_exploded)
+ {
+ draw_exploded_missile (cr, m);
+ }
+ else
+ {
+ cairo_pattern_t *pat;
+
+ double alpha = ((double) m->ticks_to_live) / MISSILE_TICKS_TO_LIVE;
+ // non-linear scaling so things don't fade out too fast
+ alpha = 1.0 - (1.0 - alpha) * (1.0 - alpha);
+
+ cairo_save (cr);
+ cairo_move_to (cr, 0, -4);
+ cairo_curve_to (cr, 3, -4, 4, -2, 4, 0);
+ cairo_curve_to (cr, 4, 4, 2, 10, 0, 18);
+ // half way point
+ cairo_curve_to (cr, -2, 10, -4, 4, -4, 0);
+ cairo_curve_to (cr, -4, -2, -3, -4, 0, -4);
+
+ pat = cairo_pattern_create_linear (0.0, -5.0, 0.0, 5.0);
+ cairo_pattern_add_color_stop_rgba (pat, 0,
+ m->primary_color.r,
+ m->primary_color.g,
+ m->primary_color.b, alpha);
+ cairo_pattern_add_color_stop_rgba (pat, 1, m->secondary_color.r,
+ m->secondary_color.g,
+ m->secondary_color.b, alpha);
+
+ cairo_set_source (cr, pat);
+ cairo_fill (cr);
+ cairo_pattern_destroy (pat);
+ cairo_restore (cr);
+
+ cairo_save (cr);
+ cairo_arc (cr, 0, 0, 3, 0, TWO_PI);
+
+ pat = cairo_pattern_create_linear (0, 3, 0, -3);
+ cairo_pattern_add_color_stop_rgba (pat, 0,
+ m->primary_color.r,
+ m->primary_color.g,
+ m->primary_color.b, alpha);
+ cairo_pattern_add_color_stop_rgba (pat, 1, m->secondary_color.r,
+ m->secondary_color.g,
+ m->secondary_color.b, alpha);
+
+ cairo_set_source (cr, pat);
+ cairo_fill (cr);
+ cairo_pattern_destroy (pat);
+ cairo_restore (cr);
+ }
+
+ cairo_restore (cr);
+}
+
+//------------------------------------------------------------------------------
+
+static void
+draw_exploded_missile (cairo_t * cr, missile_t * m)
+{
+ double alpha;
+ cairo_pattern_t *pat;
+
+ cairo_save (cr);
+ cairo_scale (cr, GLOBAL_SHIP_SCALE_FACTOR, GLOBAL_SHIP_SCALE_FACTOR);
+
+ alpha = ((double) m->ticks_to_live) / MISSILE_EXPLOSION_TICKS_TO_LIVE;
+ alpha = 1.0 - (1.0 - alpha) * (1.0 - alpha);
+
+ cairo_arc (cr, 0, 0, 30, 0, TWO_PI);
+
+ pat = cairo_pattern_create_radial (0, 0, 0, 0, 0, 30);
+ cairo_pattern_add_color_stop_rgba (pat, 0,
+ m->primary_color.r, m->primary_color.g,
+ m->primary_color.b, alpha);
+ cairo_pattern_add_color_stop_rgba (pat, 0.5, m->secondary_color.r,
+ m->secondary_color.g,
+ m->secondary_color.b, alpha * 0.75);
+ cairo_pattern_add_color_stop_rgba (pat, 1, 0, 0, 0, 0);
+
+ cairo_set_source (cr, pat);
+ cairo_fill (cr);
+ cairo_pattern_destroy (pat);
+ cairo_restore (cr);
+}
+
+//------------------------------------------------------------------------------
+
+static void
+draw_star (cairo_t * cr)
+{
+ int a = NUMBER_OF_ROTATION_ANGLES / 10;
+ float r1 = 5.0;
+ float r2 = 2.0;
+ float c;
+ int i;
+
+ cairo_save (cr);
+ cairo_move_to (cr, r1 * cos_table[0] / FIXED_POINT_SCALE_FACTOR,
+ r1 * sin_table[0] / FIXED_POINT_SCALE_FACTOR);
+
+ for (i = 0; i < 5; i++) {
+ cairo_line_to (cr, r1 * cos_table[0] / FIXED_POINT_SCALE_FACTOR,
+ r1 * sin_table[0] / FIXED_POINT_SCALE_FACTOR);
+ cairo_line_to (cr, r2 * cos_table[a] / FIXED_POINT_SCALE_FACTOR,
+ r2 * sin_table[a] / FIXED_POINT_SCALE_FACTOR);
+ cairo_rotate (cr, 4*a*PI/NUMBER_OF_ROTATION_ANGLES);
+ }
+
+ cairo_close_path (cr);
+ cairo_restore (cr);
+
+ c = 0.5;
+ cairo_set_source_rgb (cr, c, c, c);
+ cairo_fill (cr);
+}
+
+//------------------------------------------------------------------------------
+
+static gint
+on_timeout (gpointer data)
+{
+ int i;
+
+ player1.is_hit = FALSE;
+ player2.is_hit = FALSE;
+
+ apply_physics_to_player (&player1);
+ apply_physics_to_player (&player2);
+
+ if (check_for_collision (&(player1.p), &(player2.p)))
+ {
+ int p1vx;
+ int p1vy;
+ int p2vx;
+ int p2vy;
+
+ int dvx;
+ int dvy;
+ int dv2;
+ int damage;
+
+ enforce_minimum_distance (&(player1.p), &(player2.p));
+
+ p1vx = player1.p.vx;
+ p1vy = player1.p.vy;
+ p2vx = player2.p.vx;
+ p2vy = player2.p.vy;
+
+ dvx = (p1vx - p2vx) / FIXED_POINT_HALF_SCALE_FACTOR;
+ dvy = (p1vy - p2vy) / FIXED_POINT_HALF_SCALE_FACTOR;
+ dv2 = (dvx * dvx) + (dvy * dvy);
+ damage = ((int)(sqrt (dv2))) / DAMAGE_PER_SHIP_BOUNCE_DIVISOR;
+
+ player1.energy -= damage;
+ player2.energy -= damage;
+ player1.is_hit = TRUE;
+ player2.is_hit = TRUE;
+
+ player1.p.vx = (p1vx * -2 / 8) + (p2vx * +5 / 8);
+ player1.p.vy = (p1vy * -2 / 8) + (p2vy * +5 / 8);
+ player2.p.vx = (p1vx * +5 / 8) + (p2vx * -2 / 8);
+ player2.p.vy = (p1vy * +5 / 8) + (p2vy * -2 / 8);
+ }
+
+ for (i = 0; i < MAX_NUMBER_OF_MISSILES; i++)
+ {
+ if (missiles[i].is_alive)
+ {
+ apply_physics (&(missiles[i].p));
+
+ if (!missiles[i].has_exploded)
+ {
+ if (check_for_collision (&(missiles[i].p), &(player1.p)))
+ {
+ on_collision (&player1, &(missiles[i]));
+ }
+
+ if (check_for_collision (&(missiles[i].p), &(player2.p)))
+ {
+ on_collision (&player2, &(missiles[i]));
+ }
+ }
+
+ missiles[i].ticks_to_live--;
+ if (missiles[i].ticks_to_live <= 0)
+ {
+ missiles[i].is_alive = FALSE;
+ }
+ }
+ }
+
+ if (player1.energy <= 0)
+ {
+ player1.energy = 0;
+ player1.is_dead = TRUE;
+ }
+ else
+ {
+ player1.energy = MIN (SHIP_MAX_ENERGY, player1.energy + 1);
+ }
+
+ if (player2.energy <= 0)
+ {
+ player2.energy = 0;
+ player2.is_dead = TRUE;
+ }
+ else
+ {
+ player2.energy = MIN (SHIP_MAX_ENERGY, player2.energy + 1);
+ }
+
+ gtk_widget_queue_draw ((GtkWidget *) data);
+ return TRUE;
+}
+
+//------------------------------------------------------------------------------
+
+static void
+apply_physics_to_player (player_t * player)
+{
+ int v2, m2;
+ physics_t *p = &(player->p);
+
+ if (!player->is_dead)
+ {
+ // check if player is turning left, ...
+ if (player->is_turning_left)
+ {
+ p->rotation--;
+ while (p->rotation < 0)
+ {
+ p->rotation += NUMBER_OF_ROTATION_ANGLES;
+ }
+ }
+
+ // ... or right.
+ if (player->is_turning_right)
+ {
+ p->rotation++;
+ while (p->rotation >= NUMBER_OF_ROTATION_ANGLES)
+ {
+ p->rotation -= NUMBER_OF_ROTATION_ANGLES;
+ }
+ }
+
+ // check if accelerating
+ if (player->is_thrusting)
+ {
+ p->vx += SHIP_ACCELERATION_FACTOR * cos_table[p->rotation];
+ p->vy += SHIP_ACCELERATION_FACTOR * sin_table[p->rotation];
+ }
+
+ // apply velocity upper bound
+ v2 = ((p->vx) * (p->vx)) + ((p->vy) * (p->vy));
+ m2 = SHIP_MAX_VELOCITY * SHIP_MAX_VELOCITY;
+ if (v2 > m2)
+ {
+ p->vx = (int) (((double) (p->vx) * m2) / v2);
+ p->vy = (int) (((double) (p->vy) * m2) / v2);
+ }
+
+ // check if player is shooting
+ if (player->ticks_until_can_fire == 0)
+ {
+ if ((player->is_firing) && (player->energy > ENERGY_PER_MISSILE))
+ {
+ int xx = cos_table[p->rotation];
+ int yy = sin_table[p->rotation];
+
+ missile_t *m = &(missiles[next_missile_index++]);
+
+ player->energy -= ENERGY_PER_MISSILE;
+
+ if (next_missile_index == MAX_NUMBER_OF_MISSILES)
+ {
+ next_missile_index = 0;
+ }
+
+ m->p.x =
+ p->x +
+ (((SHIP_RADIUS +
+ MISSILE_RADIUS) / FIXED_POINT_SCALE_FACTOR) * xx);
+ m->p.y =
+ p->y +
+ (((SHIP_RADIUS +
+ MISSILE_RADIUS) / FIXED_POINT_SCALE_FACTOR) * yy);
+ m->p.vx = p->vx + (MISSILE_SPEED * xx);
+ m->p.vy = p->vy + (MISSILE_SPEED * yy);
+ m->p.rotation = p->rotation;
+ m->ticks_to_live = MISSILE_TICKS_TO_LIVE;
+ m->primary_color = player->primary_color;
+ m->secondary_color = player->secondary_color;
+ m->is_alive = TRUE;
+ m->has_exploded = FALSE;
+
+ player->ticks_until_can_fire += TICKS_BETWEEN_FIRE;
+ }
+ }
+ else
+ {
+ player->ticks_until_can_fire--;
+ }
+ }
+
+ // apply velocity deltas to displacement
+ apply_physics (p);
+}
+
+//------------------------------------------------------------------------------
+
+static void
+apply_physics (physics_t * p)
+{
+ p->x += p->vx;
+ while (p->x > (WIDTH * FIXED_POINT_SCALE_FACTOR))
+ {
+ p->x -= (WIDTH * FIXED_POINT_SCALE_FACTOR);
+ }
+ while (p->x < 0)
+ {
+ p->x += (WIDTH * FIXED_POINT_SCALE_FACTOR);
+ }
+
+ p->y += p->vy;
+ while (p->y > (HEIGHT * FIXED_POINT_SCALE_FACTOR))
+ {
+ p->y -= (HEIGHT * FIXED_POINT_SCALE_FACTOR);
+ }
+ while (p->y < 0)
+ {
+ p->y += (HEIGHT * FIXED_POINT_SCALE_FACTOR);
+ }
+}
+
+//------------------------------------------------------------------------------
+
+static gboolean
+check_for_collision (physics_t * p1, physics_t * p2)
+{
+ int dx = (p1->x - p2->x) / FIXED_POINT_HALF_SCALE_FACTOR;
+ int dy = (p1->y - p2->y) / FIXED_POINT_HALF_SCALE_FACTOR;
+ int r = (p1->radius + p2->radius) / FIXED_POINT_HALF_SCALE_FACTOR;
+ int d2 = (dx * dx) + (dy * dy);
+ return (d2 < (r * r)) ? TRUE : FALSE;
+}
+
+//------------------------------------------------------------------------------
+
+static void
+enforce_minimum_distance (physics_t * p1, physics_t * p2)
+{
+ int dx = p1->x - p2->x;
+ int dy = p1->y - p2->y;
+ double d2 = (((double) dx) * dx) + (((double) dy) * dy);
+ int d = (int) sqrt (d2);
+
+ int r = p1->radius + p2->radius;
+
+ // normalize dx and dy to length = ((r - d) / 2) + fudge_factor
+ int desired_vector_length = ((r - d) * 5) / 8;
+
+ dx *= desired_vector_length;
+ dy *= desired_vector_length;
+ dx /= d;
+ dy /= d;
+
+ p1->x += dx;
+ p1->y += dy;
+ p2->x -= dx;
+ p2->y -= dy;
+}
+
+//------------------------------------------------------------------------------
+
+static void
+on_collision (player_t * p, missile_t * m)
+{
+ p->energy -= DAMAGE_PER_MISSILE;
+ p->is_hit = TRUE;
+ m->has_exploded = TRUE;
+ m->ticks_to_live = MISSILE_EXPLOSION_TICKS_TO_LIVE;
+ m->p.vx = 0;
+ m->p.vy = 0;
+}
+
+//------------------------------------------------------------------------------
+
+static void
+show_text_message (cairo_t * cr, int font_size, int dy, const char *message)
+{
+ double x, y;
+ cairo_text_extents_t extents;
+
+ cairo_save (cr);
+
+ cairo_select_font_face (cr, "Serif",
+ CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
+
+ cairo_set_font_size (cr, font_size);
+ cairo_text_extents (cr, message, &extents);
+ x = (WIDTH / 2) - (extents.width / 2 + extents.x_bearing);
+ y = (HEIGHT / 2) - (extents.height / 2 + extents.y_bearing);
+
+ cairo_set_source_rgba (cr, 1, 1, 1, 1);
+ cairo_move_to (cr, x, y + dy);
+ cairo_show_text (cr, message);
+ cairo_restore (cr);
+}
+
+//------------------------------------------------------------------------------
+
+static void
+reset ()
+{
+ player1.p.x = 200 * FIXED_POINT_SCALE_FACTOR;
+ player1.p.y = 200 * FIXED_POINT_SCALE_FACTOR;
+ player1.p.vx = 0;
+ player1.p.vy = 0;
+ player1.p.rotation = random () % NUMBER_OF_ROTATION_ANGLES;
+ player1.p.radius = SHIP_RADIUS;
+ player1.is_thrusting = FALSE;
+ player1.is_turning_left = FALSE;
+ player1.is_turning_right = FALSE;
+ player1.is_firing = FALSE;
+ player1.primary_color.r = 0.3;
+ player1.primary_color.g = 0.5;
+ player1.primary_color.b = 0.9;
+ player1.secondary_color.r = 0.1;
+ player1.secondary_color.g = 0.3;
+ player1.secondary_color.b = 0.3;
+ player1.ticks_until_can_fire = 0;
+ player1.energy = SHIP_MAX_ENERGY;
+ player1.is_hit = FALSE;
+ player1.is_dead = FALSE;
+
+ player2.p.x = 600 * FIXED_POINT_SCALE_FACTOR;
+ player2.p.y = 400 * FIXED_POINT_SCALE_FACTOR;
+ player2.p.vx = 0;
+ player2.p.vy = 0;
+ player2.p.rotation = random () % NUMBER_OF_ROTATION_ANGLES;
+ player2.p.radius = SHIP_RADIUS;
+ player2.is_thrusting = FALSE;
+ player2.is_turning_left = FALSE;
+ player2.is_turning_right = FALSE;
+ player2.is_firing = FALSE;
+ player2.primary_color.r = 0.9;
+ player2.primary_color.g = 0.2;
+ player2.primary_color.b = 0.3;
+ player2.secondary_color.r = 0.5;
+ player2.secondary_color.g = 0.2;
+ player2.secondary_color.b = 0.3;
+ player2.ticks_until_can_fire = 0;
+ player2.energy = SHIP_MAX_ENERGY;
+ player2.is_hit = FALSE;
+ player2.is_dead = FALSE;
+
+ init_stars_array ();
+ init_missiles_array ();
+
+ game_over_message = NULL;
+}
+
+//------------------------------------------------------------------------------
+
+static gint
+on_key_press (GtkWidget * widget, GdkEventKey * event)
+{
+ return on_key_event (widget, event, TRUE);
+}
+
+//------------------------------------------------------------------------------
+
+static gint
+on_key_release (GtkWidget * widget, GdkEventKey * event)
+{
+ return on_key_event (widget, event, FALSE);
+}
+
+//------------------------------------------------------------------------------
+
+static gint
+on_key_event (GtkWidget * widget, GdkEventKey * event, gboolean key_is_on)
+{
+ switch (event->keyval)
+ {
+ case GDK_Escape:
+ gtk_main_quit ();
+ break;
+
+ case GDK_bracketleft:
+ if (key_is_on)
+ {
+ debug_scale_factor /= 1.25f;
+ printf ("Scale: %f\n", debug_scale_factor);
+ }
+ break;
+ case GDK_bracketright:
+ if (key_is_on)
+ {
+ debug_scale_factor *= 1.25f;
+ printf ("Scale: %f\n", debug_scale_factor);
+ }
+ break;
+
+ case GDK_space:
+ if (game_over_message != NULL)
+ {
+ reset ();
+ }
+ break;
+
+ case GDK_a:
+ player1.is_turning_left = key_is_on;
+ break;
+ case GDK_d:
+ player1.is_turning_right = key_is_on;
+ break;
+ case GDK_w:
+ player1.is_thrusting = key_is_on;
+ break;
+ case GDK_Control_L:
+ player1.is_firing = key_is_on;
+ break;
+
+ case GDK_Left:
+ case GDK_KP_Left:
+ player2.is_turning_left = key_is_on;
+ break;
+ case GDK_Right:
+ case GDK_KP_Right:
+ player2.is_turning_right = key_is_on;
+ break;
+ case GDK_Up:
+ case GDK_KP_Up:
+ player2.is_thrusting = key_is_on;
+ break;
+ case GDK_Control_R:
+ case GDK_KP_Insert:
+ player2.is_firing = key_is_on;
+ break;
+ }
+ return TRUE;
+}