// 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 #include #include #include #include #include #include #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; }