From 9028f1af74323e2df6523ad67a28bb718f20869b Mon Sep 17 00:00:00 2001 From: Antonio Carzaniga Date: Sat, 11 Dec 2021 21:32:48 +0100 Subject: [PATCH] modularization: main, game, balls, c_index, spaceship, gravity --- Makefile | 15 +- balls.c | 491 +++++++--------------------------------------------- balls.h | 23 ++- c_index.c | 1 + game.c | 20 +++ game.h | 31 ++++ gravity.c | 39 +++++ gravity.h | 11 ++ main.c | 279 +++++++++++++++++++++++++++++ spaceship.c | 72 ++++++++ spaceship.h | 11 ++ stats.c | 7 + stats.h | 24 +++ 13 files changed, 587 insertions(+), 437 deletions(-) create mode 100644 game.c create mode 100644 game.h create mode 100644 gravity.c create mode 100644 gravity.h create mode 100644 main.c create mode 100644 spaceship.c create mode 100644 spaceship.h create mode 100644 stats.c create mode 100644 stats.h diff --git a/Makefile b/Makefile index bbb4902..af9a5b3 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,6 @@ +.PHONY: default +default: all + GTK_PACKAGES=gdk-pixbuf-2.0 gtk+-3.0 GTK_CFLAGS=$(shell pkg-config --cflags $(GTK_PACKAGES)) GTK_LIBS=$(shell pkg-config --libs $(GTK_PACKAGES)) @@ -8,10 +11,16 @@ CFLAGS=-Wall -g -O2 $(PROFILING_CFLAGS) $(GTK_CFLAGS) LIBS=$(GTK_LIBS) -lm PROGS=balls -OBJS=balls.o c_index.o +OBJS=balls.o c_index.o game.o gravity.o spaceship.o main.o -.PHONY: default -default: all +# dependencies (gcc -MM *.c) +balls.o: balls.c game.h balls.h gravity.h +c_index.o: c_index.c balls.h game.h c_index.h +game.o: game.c game.h +gravity.o: gravity.c gravity.h game.h +main.o: main.c game.h balls.h c_index.h gravity.h spaceship.h +spaceship.o: spaceship.c balls.h game.h +stats.o: stats.c .PHONY: run run: balls diff --git a/balls.c b/balls.c index 70b4cbf..240e8bf 100644 --- a/balls.c +++ b/balls.c @@ -1,22 +1,10 @@ -#include #include -#include #include -#include - -#include -#include - -#define DEFAULT_WIDTH 800 -#define DEFAULT_HEIGHT 800 +#include +#include "game.h" #include "balls.h" -#include "c_index.h" - -double delta = 0.01; /* seconds */ - -unsigned int width = DEFAULT_WIDTH; -unsigned int height = DEFAULT_HEIGHT; +#include "gravity.h" unsigned int radius_min = 5; unsigned int radius_max = 10; @@ -30,10 +18,7 @@ unsigned int v_angle_max = 100; struct ball * balls = 0; unsigned int n_balls = 50; -double g_y = 20; -double g_x = 0; - -void random_velocity(struct ball * p) { +static void random_velocity(struct ball * p) { double r2; do { p->v_x = v_min + rand() % (v_max + 1 - v_min); @@ -42,23 +27,7 @@ void random_velocity(struct ball * p) { } while (r2 > v_max*v_max || r2 < v_min*v_min); } -struct ball spaceship; -double spaceship_thrust = 0; -int spaceship_thrust_countdown = 0; -int spaceship_thrust_init = 50; - -void spaceship_init_state () { - spaceship.x = width/2; - spaceship.y = height/2; - spaceship.radius = 30; - spaceship.v_x = 0; - spaceship.v_y = 0; - spaceship.angle = 0; - spaceship.v_angle = 0; -} - void balls_init_state () { - srand(time(NULL)); static const unsigned int border = 10; unsigned int w = width < 2*border ? 1 : width - 2*border; unsigned int h = height < 2*border ? 1 : height - 2*border; @@ -78,31 +47,6 @@ void balls_init_state () { } } -void ball_elastic_collision (struct ball * p, struct ball * q) { - double dx = q->x - p->x; - double dy = q->y - p->y; - double d2 = dx*dx + dy*dy; - double r = p->radius + q->radius; - if (d2 <= r*r) { - double dv_x = q->v_x - p->v_x; - double dv_y = q->v_y - p->v_y; - - double mp = p->radius * p->radius; - double mq = q->radius * q->radius; - - double f = dv_x*dx + dv_y*dy; - - if (f < 0) { - f /= d2*(mp + mq); - p->v_x += 2*mq*f*dx; - p->v_y += 2*mq*f*dy; - - q->v_x -= 2*mp*f*dx; - q->v_y -= 2*mp*f*dy; - } - } -} - void ball_update_state (struct ball * p) { p->x += delta*p->v_x + delta*delta*g_x/2.0; p->v_x += delta*g_x; @@ -140,90 +84,40 @@ void ball_update_state (struct ball * p) { p->angle += 2*M_PI; } -void spaceship_update_state () { - if (spaceship_thrust > 0) { - double fx = cos(spaceship.angle)*spaceship_thrust*4.0; - double fy = sin(spaceship.angle)*spaceship_thrust*4.0; +void ball_elastic_collision (struct ball * p, struct ball * q) { + double dx = q->x - p->x; + double dy = q->y - p->y; + double d2 = dx*dx + dy*dy; + double r = p->radius + q->radius; + if (d2 <= r*r) { + double dv_x = q->v_x - p->v_x; + double dv_y = q->v_y - p->v_y; - spaceship.x += delta*delta*fx/2.0; - spaceship.v_x += delta*fx; - spaceship.y += delta*delta*fy/2.0; - spaceship.v_y += delta*fy; - if (spaceship_thrust_countdown > 0) - --spaceship_thrust_countdown; - else - spaceship_thrust = 0; - } - ball_update_state(&spaceship); -} + double mp = p->radius * p->radius; + double mq = q->radius * q->radius; -void reposition_within_borders () { - for(int i = 0; i < n_balls; ++i) { - struct ball * p = balls + i; - if (p->x < p->radius) - p->x = p->radius; - else if (p->x + p->radius > width) - p->x = width - p->radius; - if (p->y < p->radius) - p->y = p->radius; - else if (p->y + p->radius > height) - p->y = height - p->radius; + double f = dv_x*dx + dv_y*dy; + + if (f < 0) { + f /= d2*(mp + mq); + p->v_x += 2*mq*f*dx; + p->v_y += 2*mq*f*dy; + + q->v_x -= 2*mp*f*dx; + q->v_y -= 2*mp*f*dy; + } } } -void movement_and_borders () { - for(int i = 0; i < n_balls; ++i) - ball_update_state(balls + i); - spaceship_update_state(); -} - -/* Trivial collision check - */ - -void check_collisions_simple () { - for(int i = 0; i < n_balls; ++i) - for(int j = i + 1; j < n_balls; ++j) - ball_elastic_collision(balls + i, balls + j); - for(int j = 0; j < n_balls; ++j) - ball_elastic_collision(&spaceship, balls + j); -} - -void check_collisions_with_index () { - c_index_build(); - c_index_check_collisions(ball_elastic_collision); -} - -void (*check_collisions)() = 0; - -void update_state () { - if (check_collisions) - check_collisions(); - movement_and_borders(); -} - -/* Graphics System - */ - -GtkWidget * canvas; - -int gravity_vector_countdown = 0; -int gravity_vector_init = 300; - -void draw_gravity_vector(cairo_t * cr) { - if (gravity_vector_countdown != 0) { - cairo_save(cr); - cairo_new_path(cr); - cairo_move_to(cr, width/2, height/2); - cairo_line_to(cr, width/2 + g_x, height/2 + g_y); - cairo_set_source_rgb(cr, 1.0, 1.0, 1.0); - cairo_set_line_width(cr, 1.0); - cairo_stroke(cr); - cairo_arc(cr, width/2 + g_x, height/2 + g_y, 3, 0, 2*M_PI); - cairo_fill(cr); - if (gravity_vector_countdown > 0) - --gravity_vector_countdown; - cairo_restore(cr); - } +void ball_reposition (struct ball * b) { + if (b->x < b->radius) + b->x = b->radius; + else if (b->x + b->radius > width) + b->x = width - b->radius; + if (b->y < b->radius) + b->y = b->radius; + else if (b->y + b->radius > height) + b->y = height - b->radius; } const char * face_filename = 0; @@ -231,8 +125,8 @@ int face_rotation = 0; static const double linear_rotation_unit = 2.0; -unsigned int faces_count; -struct ball_face ** faces; +static unsigned int faces_count; +static struct ball_face ** faces; struct ball_face { unsigned int rotations; @@ -243,7 +137,7 @@ static double random_color_component() { return 1.0*(rand() % 200 + 56)/255; }; -struct ball_face * new_ball_face(unsigned int radius, cairo_surface_t * face, int rotation) { +static struct ball_face * new_ball_face(unsigned int radius, cairo_surface_t * face, int rotation) { struct ball_face * f = malloc(sizeof(struct ball_face)); if (!f) return 0; @@ -293,7 +187,7 @@ struct ball_face * new_ball_face(unsigned int radius, cairo_surface_t * face, in return f; } -void init_graphics() { +static void balls_init_faces () { cairo_surface_t * face_surface = 0; if (face_filename) { @@ -324,47 +218,7 @@ void init_graphics() { } } -void destroy_graphics() { - if (!faces) - return; - for (int i = 0; i < faces_count; ++i) { - if (faces[i]) { - if (faces[i]->c_faces) { - for (unsigned int j = 0; j < faces[i]->rotations; ++j) - cairo_surface_destroy(faces[i]->c_faces[j]); - free(faces[i]->c_faces); - } - free(faces[i]); - } - } - free(faces); - faces = 0; - faces_count = 0; -} - -void draw_space_ship (cairo_t * cr) { - static const double one_over_sqrt_2 = 0.70710678118654752440; - cairo_save(cr); - cairo_set_source_rgba(cr, 0.0, 0.0, 1.0, 1.0); - cairo_translate(cr, spaceship.x, spaceship.y); - cairo_rotate(cr, spaceship.angle); - cairo_arc(cr, 0, 0, spaceship.radius, 0, 2*M_PI); - cairo_stroke(cr); - cairo_set_source_rgba(cr, 0.0, 1.0, 0.0, 1.0); - cairo_move_to (cr, 0, 0); - cairo_line_to (cr, -one_over_sqrt_2*spaceship.radius, one_over_sqrt_2*spaceship.radius); - cairo_line_to (cr, spaceship.radius, 0); - cairo_line_to (cr, -one_over_sqrt_2*spaceship.radius, -one_over_sqrt_2*spaceship.radius); - cairo_line_to (cr, 0, 0); - cairo_stroke(cr); - for (unsigned int i = 0; i < spaceship_thrust; i += 5) { - cairo_arc(cr, 0, 0, spaceship.radius + i, 0.7*M_PI, 1.3*M_PI); - cairo_stroke(cr); - } - cairo_restore(cr); -} - -void draw_balls (cairo_t * cr) { +void balls_draw (cairo_t * cr) { for (const struct ball * b = balls; b != balls + n_balls; ++b) { cairo_save(cr); cairo_translate(cr, b->x - b->radius, b->y - b->radius); @@ -383,257 +237,32 @@ void draw_balls (cairo_t * cr) { } } -gboolean draw_frame (GtkWidget * widget, cairo_t *cr, gpointer data) { - cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); - cairo_paint(cr); - draw_gravity_vector(cr); - draw_balls(cr); - draw_space_ship(cr); - return FALSE; -} - -gint configure_event (GtkWidget *widget, GdkEventConfigure * event) { - if (width == gtk_widget_get_allocated_width(widget) && height == gtk_widget_get_allocated_height(widget)) - return FALSE; - - width = gtk_widget_get_allocated_width(widget); - height = gtk_widget_get_allocated_height(widget); - - reposition_within_borders(); - return TRUE; -} - -gint keyboard_input (GtkWidget *widget, GdkEventKey *event) { - if (event->type != GDK_KEY_PRESS) - return FALSE; - switch(event->keyval) { - case GDK_KEY_Up: - g_y -= 10; - gravity_vector_countdown = gravity_vector_init; - break; - case GDK_KEY_Down: - g_y += 10; - gravity_vector_countdown = gravity_vector_init; - break; - case GDK_KEY_Left: - g_x -= 10; - gravity_vector_countdown = gravity_vector_init; - break; - case GDK_KEY_Right: - g_x += 10; - gravity_vector_countdown = gravity_vector_init; - break; - case GDK_KEY_G: - case GDK_KEY_g: - gravity_vector_countdown = gravity_vector_init; - break; - case GDK_KEY_Q: - case GDK_KEY_q: - gtk_main_quit(); - break; - default: - return FALSE; - } - return TRUE; -} - -gboolean mouse_scroll (GtkWidget *widget, GdkEvent *event, gpointer user_data) { - if (event->type == GDK_SCROLL) { - GdkEventScroll * e = (GdkEventScroll*) event; - switch (e->direction) { - case GDK_SCROLL_SMOOTH: { - double dx, dy; - gdk_event_get_scroll_deltas (event, &dx, &dy); - spaceship.angle -= dx/4; - if (spaceship.angle < 0) - spaceship.angle += 2*M_PI; - else if (spaceship.angle > 2*M_PI) - spaceship.angle -= 2*M_PI; - spaceship_thrust += dy; - if (spaceship_thrust > 0) - spaceship_thrust_countdown = spaceship_thrust_init; - else - spaceship_thrust = 0; - break; - } - case GDK_SCROLL_LEFT: - g_x -= 10; - gravity_vector_countdown = gravity_vector_init; - break; - case GDK_SCROLL_RIGHT: - g_x += 10; - gravity_vector_countdown = gravity_vector_init; - break; - case GDK_SCROLL_UP: - g_y += 10; - gravity_vector_countdown = gravity_vector_init; - break; - case GDK_SCROLL_DOWN: - g_y -= 10; - gravity_vector_countdown = gravity_vector_init; - break; - } - } - return TRUE; -} - -void destroy_window (void) { - gtk_main_quit(); -} - -void print_usage (const char * progname) { - fprintf(stderr, - "usage: %s [options...]\n" - "options:\n" - "\tx\n" - "\tn=\n" - "\tfx=\n" - "\tfy=\n" - "\tradius=-\n" - "\tv=-\n" - "\tdelta= (in seconds)\n" - "\tface=\n" - "\tstats= :: rendering timing statitstics (0=disabled, default)\n" - "\tcollisions= :: n=no collisions, s=simple, i=index\n" - "\t-r :: activate face rotation\n", - progname); -} - -static unsigned int stats_sampling = 0; -static guint64 stats_update_usec = 0; -static unsigned int stats_update_samples = 0; -static guint64 stats_draw_usec = 0; -static unsigned int stats_draw_samples = 0; - -gboolean draw_event (GtkWidget *widget, cairo_t * cr, gpointer data) { - if (stats_sampling > 0) { - guint64 start = g_get_monotonic_time (); - draw_frame (widget, cr, data); - stats_draw_usec += g_get_monotonic_time () - start; - ++stats_draw_samples; - } else - draw_frame (widget, cr, data); - return FALSE; -} - -gboolean timeout (gpointer user_data) { - if (stats_sampling > 0) { - guint64 start = g_get_monotonic_time (); - update_state(); - stats_update_usec += g_get_monotonic_time () - start; - if (++stats_update_samples == stats_sampling) { - float uavg = 1.0*stats_update_usec / stats_update_samples; - float davg = 1.0*stats_draw_usec / stats_draw_samples; - printf("\rupdate = %.0f us, draw = %.0f us, load = %.0f%% (%u update, %u draw) ", - uavg, davg, (uavg+davg)/(10000.0*delta), - stats_update_samples, stats_draw_samples); - fflush(stdout); - stats_update_usec = 0; - stats_update_samples = 0; - stats_draw_usec = 0; - stats_draw_samples = 0; - } - } else { - update_state(); - } - gtk_widget_queue_draw(canvas); - return TRUE; -} - -int main (int argc, const char *argv[]) { - int w = DEFAULT_WIDTH; - int h = DEFAULT_HEIGHT; - - for (int i = 1; i < argc; ++i) { - if (sscanf(argv[i], "%dx%d", &w, &h) == 2) - continue; - if (sscanf(argv[i], "n=%u", &n_balls) == 1) - continue; - if (sscanf(argv[i], "fx=%lf", &g_x) == 1) - continue; - if (sscanf(argv[i], "fy=%lf", &g_y) == 1) - continue; - if (sscanf(argv[i], "radius=%u-%u", &radius_min, &radius_max) == 2) - continue; - if (sscanf(argv[i], "v=%u-%u", &v_min, &v_max) == 2) - continue; - if (sscanf(argv[i], "delta=%lf", &delta) == 1) - continue; - if (strncmp(argv[i], "face=", 5) == 0) { - face_filename = argv[i] + 5; - continue; - } - if (sscanf(argv[i], "stats=%u", &stats_sampling) == 1) - continue; - char collisions; - if (sscanf(argv[i], "collisions=%c", &collisions) == 1) { - switch (collisions) { - case 'i': - case 'I': - check_collisions = check_collisions_with_index; - continue; - case '0': - case 'N': - case 'n': - check_collisions = 0; - continue; - case 's': - case 'S': - check_collisions = check_collisions_simple; - continue; +static void balls_destroy_faces () { + if (!faces) + return; + for (int i = 0; i < faces_count; ++i) { + if (faces[i]) { + if (faces[i]->c_faces) { + for (unsigned int j = 0; j < faces[i]->rotations; ++j) + cairo_surface_destroy(faces[i]->c_faces[j]); + free(faces[i]->c_faces); } + free(faces[i]); } - if (strcmp(argv[i], "-r") == 0) { - face_rotation = 1; - continue; - } - print_usage(argv[0]); - return 1; } + free (faces); + faces = 0; + faces_count = 0; +} +void balls_destroy () { + balls_destroy_faces (); + free (balls); +} + +void balls_init () { balls = malloc(sizeof(struct ball)*n_balls); assert(balls); - - assert(c_index_init()); - - balls_init_state(); - spaceship_init_state(); - - gtk_init(0, 0); - - GtkWidget * window = gtk_window_new (GTK_WINDOW_TOPLEVEL); - gtk_window_set_default_size(GTK_WINDOW(window), width, height); - gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); - gtk_window_set_title(GTK_WINDOW(window), "Game"); - - g_signal_connect(window, "destroy", G_CALLBACK(destroy_window), NULL); - g_signal_connect (G_OBJECT (window), "delete-event", G_CALLBACK(destroy_window), NULL); - g_signal_connect(G_OBJECT (window), "key-press-event", G_CALLBACK(keyboard_input), NULL); - gtk_widget_set_events (window, GDK_EXPOSURE_MASK | GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK | GDK_KEY_PRESS_MASK); - - canvas = gtk_drawing_area_new (); - - g_signal_connect (G_OBJECT (canvas), "configure-event", G_CALLBACK(configure_event), NULL); - g_signal_connect (G_OBJECT (canvas), "draw", G_CALLBACK (draw_event), NULL); - g_signal_connect (G_OBJECT (canvas), "scroll-event",G_CALLBACK(mouse_scroll), NULL); - gtk_widget_set_events (canvas, GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK); - - gtk_container_add (GTK_CONTAINER (window), canvas); - - g_timeout_add (delta * 1000, timeout, canvas); - - gtk_widget_show_all(window); - - init_graphics(); - - gtk_main(); - - if (stats_sampling > 0) - printf("\n"); - - destroy_graphics(); - c_index_destroy(); - free(balls); - - return 0; + balls_init_state (); + balls_init_faces (); } diff --git a/balls.h b/balls.h index 14f2ad6..880f3d3 100644 --- a/balls.h +++ b/balls.h @@ -1,6 +1,8 @@ #ifndef BALLS_H_INCLUDED #define BALLS_H_INCLUDED +#include + struct ball_face; struct ball { @@ -17,11 +19,26 @@ struct ball { struct ball_face * face; }; -extern unsigned int width; -extern unsigned int height; - extern struct ball * balls; extern unsigned int n_balls; +extern unsigned int radius_min; +extern unsigned int radius_max; + +extern unsigned int v_max; +extern unsigned int v_min; + +extern unsigned int v_angle_min; +extern unsigned int v_angle_max; + +extern const char * face_filename; +extern int face_rotation; + +extern void balls_init (); +extern void balls_destroy (); +extern void ball_update_state (struct ball * p); +extern void ball_elastic_collision (struct ball * p, struct ball * q); +extern void ball_reposition (struct ball * b); +extern void balls_draw (cairo_t * cr); #endif diff --git a/c_index.c b/c_index.c index 5831960..61ee3f5 100644 --- a/c_index.c +++ b/c_index.c @@ -1,6 +1,7 @@ #include #include "balls.h" +#include "game.h" #include "c_index.h" /* Collision check with index diff --git a/game.c b/game.c new file mode 100644 index 0000000..ab4a9f0 --- /dev/null +++ b/game.c @@ -0,0 +1,20 @@ +#include +#include +#include +#include +#include + +#include +#include + +#include "game.h" + +double delta = DEFAULT_DELTA; /* seconds */ + +unsigned int width = DEFAULT_WIDTH; +unsigned int height = DEFAULT_HEIGHT; + +/* Graphics System + */ +GtkWidget * canvas; + diff --git a/game.h b/game.h new file mode 100644 index 0000000..cba025d --- /dev/null +++ b/game.h @@ -0,0 +1,31 @@ +#ifndef GAME_H_INCLUDED +#define GAME_H_INCLUDED + +#include + +/* simulation/game framework + */ + +extern double delta; /* simulation time delta in seconds */ + +#define DEFAULT_DELTA 0.01 + +extern unsigned int width; /* game canvas width */ +extern unsigned int height; /* game canvas height */ + +#define DEFAULT_WIDTH 800 +#define DEFAULT_HEIGHT 800 + +extern GtkWidget * canvas; /* game canvas object */ + +#if 0 +extern void game_init (); /* implemented by the application */ + +/* game frame function to be provided by the game application */ +extern gboolean game_draw_frame (GtkWidget * widget, cairo_t * cr, gpointer data); + +extern void set_game_resize_callback (gboolean (*game_resize) ()); +extern void set_game_keyboard_input_callback (gint (*keyboard_input)(GtkWidget *, GdkEventKey *)); +extern void set_game_mouse_scroll_callback (gboolean (*mouse_scroll)(GtkWidget *, GdkEvent *, gpointer)); +#endif +#endif diff --git a/gravity.c b/gravity.c new file mode 100644 index 0000000..0b5eb64 --- /dev/null +++ b/gravity.c @@ -0,0 +1,39 @@ +#include +#include + +#include "gravity.h" +#include "game.h" + +double g_y = 20; +double g_x = 0; + +static int gravity_vector_countdown = 0; +static int gravity_vector_init = 300; + +void gravity_draw (cairo_t * cr) { + if (gravity_vector_countdown != 0) { + cairo_save(cr); + cairo_new_path(cr); + cairo_move_to(cr, width/2, height/2); + cairo_line_to(cr, width/2 + g_x, height/2 + g_y); + cairo_set_source_rgb(cr, 1.0, 1.0, 1.0); + cairo_set_line_width(cr, 1.0); + cairo_stroke(cr); + cairo_arc(cr, width/2 + g_x, height/2 + g_y, 3, 0, 2*M_PI); + cairo_fill(cr); + if (gravity_vector_countdown > 0) + --gravity_vector_countdown; + cairo_restore(cr); + } +} + +void gravity_show () { + gravity_vector_countdown = gravity_vector_init; +}; + +void gravity_change (double dx, double dy) { + g_x += dx; + g_y += dy; + gravity_show (); +}; + diff --git a/gravity.h b/gravity.h new file mode 100644 index 0000000..08bf4f1 --- /dev/null +++ b/gravity.h @@ -0,0 +1,11 @@ +#ifndef GRAVITY_H_INCLUDED +#define GRAVITY_H_INCLUDED + +extern double g_y; +extern double g_x; + +extern void gravity_draw (cairo_t * cr); +extern void gravity_change (double dx, double dy); +extern void gravity_show (); + +#endif diff --git a/main.c b/main.c new file mode 100644 index 0000000..bff379b --- /dev/null +++ b/main.c @@ -0,0 +1,279 @@ +#include +#include +#include +#include +#include + +#include +#include + +#define DEFAULT_WIDTH 800 +#define DEFAULT_HEIGHT 800 + +#include "game.h" +#include "balls.h" +#include "c_index.h" +#include "gravity.h" +#include "spaceship.h" + +/* Trivial collision check + */ +void check_collisions_simple () { + for(int i = 0; i < n_balls; ++i) + for(int j = i + 1; j < n_balls; ++j) + ball_elastic_collision(balls + i, balls + j); + for(int j = 0; j < n_balls; ++j) + ball_elastic_collision(&spaceship, balls + j); +} + +void check_collisions_with_index () { + c_index_build(); + c_index_check_collisions(ball_elastic_collision); + for(int j = 0; j < n_balls; ++j) + ball_elastic_collision(&spaceship, balls + j); +} + +void (*check_collisions)() = 0; + +void update_state () { + if (check_collisions) + check_collisions(); + for(int i = 0; i < n_balls; ++i) + ball_update_state(balls + i); + spaceship_update_state(); +} + +/* Graphics System + */ + +void game_init () { + srand (time(NULL)); + balls_init (); + assert(c_index_init()); + spaceship_init_state (); +} + +void game_destroy () { + c_index_destroy (); + balls_destroy (); +} + +gboolean draw_frame (GtkWidget * widget, cairo_t *cr, gpointer data) { + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 1.0); + cairo_paint(cr); + gravity_draw (cr); + balls_draw (cr); + spaceship_draw (cr); + return FALSE; +} + +gint configure_event (GtkWidget *widget, GdkEventConfigure * event) { + if (width == event->width && height == event->height) + return FALSE; + + width = gtk_widget_get_allocated_width (widget); + height = gtk_widget_get_allocated_height (widget); + + for (struct ball * b = balls; b != balls + n_balls; ++b) + ball_reposition (b); + ball_reposition (&spaceship); + return TRUE; +} + +gint keyboard_input (GtkWidget *widget, GdkEventKey *event) { + if (event->type != GDK_KEY_PRESS) + return FALSE; + switch(event->keyval) { + case GDK_KEY_Up: + gravity_change (0, -10); + break; + case GDK_KEY_Down: + gravity_change (0, 10); + break; + case GDK_KEY_Left: + gravity_change (-10, 0); + break; + case GDK_KEY_Right: + gravity_change (10, 0); + break; + case GDK_KEY_G: + case GDK_KEY_g: + gravity_show (); + break; + case GDK_KEY_Q: + case GDK_KEY_q: + gtk_main_quit(); + break; + default: + return FALSE; + } + return TRUE; +} + +gboolean mouse_scroll (GtkWidget *widget, GdkEvent *event, gpointer user_data) { + if (event->type == GDK_SCROLL) { + GdkEventScroll * e = (GdkEventScroll*) event; + switch (e->direction) { + case GDK_SCROLL_SMOOTH: { + double dx, dy; + gdk_event_get_scroll_deltas (event, &dx, &dy); + spaceship_control (dx, dy); + break; + } + default: + break; + } + } + return TRUE; +} + +void destroy_window (void) { + gtk_main_quit(); +} + +void print_usage (const char * progname) { + fprintf(stderr, + "usage: %s [options...]\n" + "options:\n" + "\tx\n" + "\tn=\n" + "\tfx=\n" + "\tfy=\n" + "\tradius=-\n" + "\tv=-\n" + "\tdelta= (in seconds)\n" + "\tface=\n" + "\tstats= :: rendering timing statitstics (0=disabled, default)\n" + "\tcollisions= :: n=no collisions, s=simple, i=index\n" + "\t-r :: activate face rotation\n", + progname); +} + +static unsigned int stats_sampling = 0; +static guint64 stats_update_usec = 0; +static unsigned int stats_update_samples = 0; +static guint64 stats_draw_usec = 0; +static unsigned int stats_draw_samples = 0; + +gboolean draw_event (GtkWidget *widget, cairo_t * cr, gpointer data) { + if (stats_sampling > 0) { + guint64 start = g_get_monotonic_time (); + draw_frame (widget, cr, data); + stats_draw_usec += g_get_monotonic_time () - start; + ++stats_draw_samples; + } else + draw_frame (widget, cr, data); + return FALSE; +} + +gboolean timeout (gpointer user_data) { + if (stats_sampling > 0) { + guint64 start = g_get_monotonic_time (); + update_state(); + stats_update_usec += g_get_monotonic_time () - start; + if (++stats_update_samples == stats_sampling) { + float uavg = 1.0*stats_update_usec / stats_update_samples; + float davg = 1.0*stats_draw_usec / stats_draw_samples; + printf("\rupdate = %.0f us, draw = %.0f us, load = %.0f%% (%u update, %u draw) ", + uavg, davg, (uavg+davg)/(10000.0*delta), + stats_update_samples, stats_draw_samples); + fflush(stdout); + stats_update_usec = 0; + stats_update_samples = 0; + stats_draw_usec = 0; + stats_draw_samples = 0; + } + } else { + update_state(); + } + gtk_widget_queue_draw(canvas); + return TRUE; +} + +int main (int argc, const char *argv[]) { + int w = DEFAULT_WIDTH; + int h = DEFAULT_HEIGHT; + + for (int i = 1; i < argc; ++i) { + if (sscanf(argv[i], "%dx%d", &w, &h) == 2) + continue; + if (sscanf(argv[i], "n=%u", &n_balls) == 1) + continue; + if (sscanf(argv[i], "fx=%lf", &g_x) == 1) + continue; + if (sscanf(argv[i], "fy=%lf", &g_y) == 1) + continue; + if (sscanf(argv[i], "radius=%u-%u", &radius_min, &radius_max) == 2) + continue; + if (sscanf(argv[i], "v=%u-%u", &v_min, &v_max) == 2) + continue; + if (sscanf(argv[i], "delta=%lf", &delta) == 1) + continue; + if (strncmp(argv[i], "face=", 5) == 0) { + face_filename = argv[i] + 5; + continue; + } + if (sscanf(argv[i], "stats=%u", &stats_sampling) == 1) + continue; + char collisions; + if (sscanf(argv[i], "collisions=%c", &collisions) == 1) { + switch (collisions) { + case 'i': + case 'I': + check_collisions = check_collisions_with_index; + continue; + case '0': + case 'N': + case 'n': + check_collisions = 0; + continue; + case 's': + case 'S': + check_collisions = check_collisions_simple; + continue; + } + } + if (strcmp(argv[i], "-r") == 0) { + face_rotation = 1; + continue; + } + print_usage(argv[0]); + return 1; + } + + gtk_init(0, 0); + + GtkWidget * window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_default_size(GTK_WINDOW(window), width, height); + gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_window_set_title(GTK_WINDOW(window), "Game"); + + g_signal_connect(window, "destroy", G_CALLBACK(destroy_window), NULL); + g_signal_connect (G_OBJECT (window), "delete-event", G_CALLBACK(destroy_window), NULL); + g_signal_connect(G_OBJECT (window), "key-press-event", G_CALLBACK(keyboard_input), NULL); + gtk_widget_set_events (window, GDK_EXPOSURE_MASK | GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK | GDK_KEY_PRESS_MASK); + + canvas = gtk_drawing_area_new (); + + g_signal_connect (G_OBJECT (canvas), "configure-event", G_CALLBACK(configure_event), NULL); + g_signal_connect (G_OBJECT (canvas), "draw", G_CALLBACK (draw_event), NULL); + g_signal_connect (G_OBJECT (canvas), "scroll-event",G_CALLBACK(mouse_scroll), NULL); + gtk_widget_set_events (canvas, GDK_SCROLL_MASK | GDK_SMOOTH_SCROLL_MASK); + + gtk_container_add (GTK_CONTAINER (window), canvas); + + g_timeout_add (delta * 1000, timeout, canvas); + + gtk_widget_show_all(window); + + game_init (); + + gtk_main(); + + if (stats_sampling > 0) + printf("\n"); + + game_destroy (); + + return 0; +} diff --git a/spaceship.c b/spaceship.c new file mode 100644 index 0000000..d60dc74 --- /dev/null +++ b/spaceship.c @@ -0,0 +1,72 @@ +#include +#include + +#include "balls.h" +#include "game.h" + +struct ball spaceship; +double spaceship_thrust = 0; +int spaceship_thrust_countdown = 0; +int spaceship_thrust_init = 50; + +void spaceship_init_state () { + spaceship.x = width/2; + spaceship.y = height/2; + spaceship.radius = 30; + spaceship.v_x = 0; + spaceship.v_y = 0; + spaceship.angle = 0; + spaceship.v_angle = 0; +} + +void spaceship_update_state () { + if (spaceship_thrust > 0) { + double fx = cos(spaceship.angle)*spaceship_thrust*4.0; + double fy = sin(spaceship.angle)*spaceship_thrust*4.0; + + spaceship.x += delta*delta*fx/2.0; + spaceship.v_x += delta*fx; + spaceship.y += delta*delta*fy/2.0; + spaceship.v_y += delta*fy; + if (spaceship_thrust_countdown > 0) + --spaceship_thrust_countdown; + else + spaceship_thrust = 0; + } + ball_update_state(&spaceship); +} + +void spaceship_draw (cairo_t * cr) { + static const double one_over_sqrt_2 = 0.70710678118654752440; + cairo_save(cr); + cairo_set_source_rgba(cr, 0.0, 0.0, 1.0, 1.0); + cairo_translate(cr, spaceship.x, spaceship.y); + cairo_rotate(cr, spaceship.angle); + cairo_arc(cr, 0, 0, spaceship.radius, 0, 2*M_PI); + cairo_stroke(cr); + cairo_set_source_rgba(cr, 0.0, 1.0, 0.0, 1.0); + cairo_move_to (cr, 0, 0); + cairo_line_to (cr, -one_over_sqrt_2*spaceship.radius, one_over_sqrt_2*spaceship.radius); + cairo_line_to (cr, spaceship.radius, 0); + cairo_line_to (cr, -one_over_sqrt_2*spaceship.radius, -one_over_sqrt_2*spaceship.radius); + cairo_line_to (cr, 0, 0); + cairo_stroke(cr); + for (unsigned int i = 0; i < spaceship_thrust; i += 5) { + cairo_arc(cr, 0, 0, spaceship.radius + i, 0.7*M_PI, 1.3*M_PI); + cairo_stroke(cr); + } + cairo_restore(cr); +} + +void spaceship_control (double dx, double dy) { + spaceship.angle -= dx/4; + if (spaceship.angle < 0) + spaceship.angle += 2*M_PI; + else if (spaceship.angle > 2*M_PI) + spaceship.angle -= 2*M_PI; + spaceship_thrust += dy; + if (spaceship_thrust > 0) + spaceship_thrust_countdown = spaceship_thrust_init; + else + spaceship_thrust = 0; +} diff --git a/spaceship.h b/spaceship.h new file mode 100644 index 0000000..91f6796 --- /dev/null +++ b/spaceship.h @@ -0,0 +1,11 @@ +#ifndef SPACESHIP_H_INCLUDED +#define SPACESHIP_H_INCLUDED + +extern struct ball spaceship; + +extern void spaceship_init_state (); +extern void spaceship_update_state (); +extern void spaceship_control (double dx, double dy); +extern void spaceship_draw (cairo_t * cr); + +#endif diff --git a/stats.c b/stats.c new file mode 100644 index 0000000..8ca594a --- /dev/null +++ b/stats.c @@ -0,0 +1,7 @@ + +static unsigned int stats_sampling = 0; +static guint64 stats_update_usec = 0; +static unsigned int stats_update_samples = 0; +static guint64 stats_draw_usec = 0; +static unsigned int stats_draw_samples = 0; + diff --git a/stats.h b/stats.h new file mode 100644 index 0000000..88722e3 --- /dev/null +++ b/stats.h @@ -0,0 +1,24 @@ +#ifndef STATS_H_INCLUDED +#define STATS_H_INCLUDED + +struct timing_stats { + guint64 usec; + unsigned int samples; + unsigned int target_samples; +}; + +static inline float stats_avg_usec (const timing_stats * s) { + return 1.0 * s->usec / s->samples; +} + +static inline + +#define STATS_TAKE_SAMPLE(s,f) \ + if ((s)->target_samples > 0) { \ + guint64 start = g_get_monotonic_time (); \ + do { f; } while 0; + (s)->usec += g_get_monotonic_time () - start; + ++(s)->samples; + } else { do { f; } while 0; } + +#endif