#include #include #include "dgl.h" #define MAX_ASTEROIDS 20 #define MAX_BULLETS 20 #define MAX_PARTICLES 50 #define BULLET_SPEED 3.0f #define MAX_BULLET_DISTANCE 120 #define FIRE_COOLDOWN_TIME 0.1f #define TURN_SPEED DEG_TO_RAD(2.0f) #define MAX_SPEED 5.0f #define THRUST 0.02f #define DEAD_TIME 1.0f #define ASTEROID_SPAWN_TIME 1.0f #define CHANCE(pct) (rnd_int(1, 100) <= (pct)) typedef enum { GAME_MODE_TITLE_SCREEN, GAME_MODE_PLAY, GAME_MODE_DEAD, GAME_MODE_GAME_OVER } GAME_MODE; typedef struct { VECTOR2F *points; int num_points; int width; int height; int color; } OBJECT; typedef struct { boolean alive; boolean large; OBJECT *object; VECTOR2F position; VECTOR2F velocity; float angle; float rotationSpeed; } ASTEROID; typedef struct { OBJECT *object; VECTOR2F position; VECTOR2F velocity; float angle; float accel; float fire_cooldown; int lives; int score; } PLAYER; typedef struct { boolean alive; OBJECT *object; VECTOR2F position; VECTOR2F velocity; float speed; float angle; float distance_left; } BULLET; typedef struct { boolean alive; VECTOR2F position; VECTOR2F velocity; float speed; float angle; float distance_left; } PARTICLE; typedef struct { int ticks; int last_ticks; int spawn_timer; int dead_timer; int time; float frame_time; GAME_MODE mode; } GAME_LOOP; OBJECT *ship; OBJECT *large_asteroid; OBJECT *small_asteroid; OBJECT *bullet; ASTEROID asteroids[MAX_ASTEROIDS]; BULLET bullets[MAX_BULLETS]; PARTICLE particles[MAX_PARTICLES]; PLAYER player; GAME_LOOP game; // -------------------------------------------------------------------------- boolean intersects(int a_x, int a_y, int a_width, int a_height, int b_x, int b_y, int b_width, int b_height); void wrap_screen(VECTOR2F *position, const OBJECT *object); OBJECT* object_create(int points, int width, int height, int color); void object_free(OBJECT *object); void object_draw(SURFACE *dest, const OBJECT *object, float x, float y, float angle); boolean object_intersects(const OBJECT *a, const VECTOR2F *a_pos, const OBJECT *b, const VECTOR2F *b_pos); void object_init_all(void); void object_free_all(void); void asteroid_reset_all(void); int asteroid_next_free(void); void asteroid_setup(int index, boolean large, float x, float y, float angle, float rotationSpeed, float speed); void asteroid_destroy(int index); void asteroid_spawn(boolean force); void asteroid_spawn_broken_parts(float x, float y); void asteroid_update_all(float elapsed); void bullet_reset_all(void); int bullet_next_free(void); void bullet_setup(int index, float x, float y, float angle, float speed); void bullet_destroy(int index); void bullet_update_all(float elapsed); void particle_reset_all(void); int particle_next_free(void); void particle_setup(int index, float x, float y, float angle, float speed, float distance); void particle_destroy(int index); void particle_update_all(float elapsed); void particle_spawn_explosion(float x, float y); void player_accelerate(void); void player_turn(float amount); void player_shoot(void); void player_spawn(void); void player_kill(void); void player_update(float elapsed); void draw_player(SURFACE *dest); void draw_asteroids(SURFACE *dest); void draw_bullets(SURFACE *dest); void draw_particles(SURFACE *dest); void draw_game_status(SURFACE *dest); void game_init_level(int num_asteroids); void game_setup_title_screen(void); void game_title_screen(void); void game_play(void); void game_dead(void); void game_over(void); void game_update_time(void); // -------------------------------------------------------------------------- // misc helper / utility functions boolean intersects(int a_x, int a_y, int a_width, int a_height, int b_x, int b_y, int b_width, int b_height) { if ((a_x + a_width) < b_x || a_x > (b_x + b_width)) return FALSE; else if ((a_y + a_height) < b_y || a_y > (b_y + b_height)) return FALSE; else return TRUE; } void wrap_screen(VECTOR2F *position, const OBJECT *object) { if (object != NULL) { if (position->x + (object->width / 2) < 0) position->x += 320 + (object->width); if (position->y + (object->height / 2) < 0) position->y += 200 + (object->height); if (position->x - (object->width / 2) > 319) position->x -= 320 + (object->width); if (position->y - (object->height / 2) > 199) position->y -= 200 + (object->height); } else { if (position->x < 0) position->x += 320; if (position->y < 0) position->y += 200; if (position->x > 319) position->x -= 320; if (position->y > 199) position->y -= 200; } } // -------------------------------------------------------------------------- // OBJECT management OBJECT* object_create(int points, int width, int height, int color) { OBJECT *o; o = (OBJECT*)malloc(sizeof(OBJECT)); o->points = (VECTOR2F*)malloc(sizeof(VECTOR2F) * points); o->num_points = points; o->width = width; o->height = height; o->color = color; return o; } void object_free(OBJECT *object) { if (!object) return; free(object->points); object->points = NULL; free(object); } void object_draw(SURFACE *dest, const OBJECT *object, float x, float y, float angle) { MATRIX33 m; VECTOR2F a, b; int i; m = matrix33_translation_2d(x, y); m = matrix33_mul(matrix33_rotation_2d(angle), m); b = matrix33_transform_2d(m, object->points[0]); for (i = 1; i < object->num_points; ++i) { a = matrix33_transform_2d(m, object->points[i]); surface_line(dest, a.x, a.y, b.x, b.y, object->color); b = a; } a = matrix33_transform_2d(m, object->points[0]); surface_line(dest, a.x, a.y, b.x, b.y, object->color); } boolean object_intersects(const OBJECT *a, const VECTOR2F *a_pos, const OBJECT *b, const VECTOR2F *b_pos) { return intersects(a_pos->x - a->width / 2, a_pos->y - a->height / 2, a->width, a->height, b_pos->x - b->width / 2, b_pos->y - b->height / 2, b->width, b->height); } void object_init_all(void) { ship = object_create(4, 20, 20, 15); ship->points[0] = vector2f(-10.0f, -8.0f); ship->points[1] = vector2f(-6.0f, 0.0f); ship->points[2] = vector2f(-10.0f, 8.0f); ship->points[3] = vector2f(10.0f, 0.0f); large_asteroid = object_create(6, 36, 36, 15); large_asteroid->points[0] = vector2f(12.0f, 10.5f); large_asteroid->points[1] = vector2f(25.5f, -9.0f); large_asteroid->points[2] = vector2f(18.0f, -15.0f); large_asteroid->points[3] = vector2f(6.0f, -9.0f); large_asteroid->points[4] = vector2f(-12.0f, -18.0f); large_asteroid->points[5] = vector2f(-10.5f, 16.5f); small_asteroid = object_create(6, 12, 12, 15); small_asteroid->points[0] = vector2f(4.0f, 3.5f); small_asteroid->points[1] = vector2f(8.5f, -3.0f); small_asteroid->points[2] = vector2f(6.0f, -5.0f); small_asteroid->points[3] = vector2f(2.0f, -3.0f); small_asteroid->points[4] = vector2f(-4.0f, -6.0f); small_asteroid->points[5] = vector2f(-3.5f, 5.5f); bullet = object_create(2, 5, 5, 15); bullet->points[0] = vector2f(-2.5f, 0.0f); bullet->points[1] = vector2f(2.5f, 0.0f); } void object_free_all(void) { object_free(ship); object_free(large_asteroid); object_free(small_asteroid); object_free(bullet); } // -------------------------------------------------------------------------- // ASTEROID management void asteroid_reset_all(void) { memset(asteroids, 0, sizeof(ASTEROID) * MAX_ASTEROIDS); } int asteroid_next_free(void) { int i; for (i = 0; i < MAX_ASTEROIDS; ++i) { if (!asteroids[i].alive) return i; } return -1; } void asteroid_setup(int index, boolean large, float x, float y, float angle, float rotationSpeed, float speed) { asteroids[index].alive = TRUE; asteroids[index].large = large; asteroids[index].position.x = x; asteroids[index].position.y = y; asteroids[index].angle = angle; asteroids[index].rotationSpeed = rotationSpeed; point_on_circle(speed, angle, &asteroids[index].velocity.x, &asteroids[index].velocity.y); if (large) asteroids[index].object = large_asteroid; else asteroids[index].object = small_asteroid; } void asteroid_destroy(int index) { asteroids[index].alive = FALSE; } void asteroid_spawn(boolean force) { int i, side, x, y; float angle, speed, rot_speed; if (CHANCE(33) || force) { i = asteroid_next_free(); if (i == -1) return; side = rnd_int(0, 3); switch (side) { // top case 0: x = rnd_int(0, 319); y = 0; break; // bottom case 1: x = rnd_int(0, 319); y = 199; break; // left case 2: x = 0; y = rnd_int(0, 199); break; // right case 3: x = 319; y = rnd_int(0, 199); break; } if (CHANCE(33)) angle = angle_between_f(x, y, player.position.x, player.position.y); else angle = DEG_TO_RAD(rnd_int(0, 359)); speed = rnd_int(1, 5) * 0.1f; rot_speed = rnd_int(0, 10) * DEG_TO_RAD(0.2f); asteroid_setup(i, rnd_int(0, 1), x, y, angle, rot_speed, speed); } } void asteroid_spawn_broken_parts(float x, float y) { int index; int i; int angle; float speed, rot_speed; float x_off, y_off; for (i = 0; i < 3; ++i) { index = asteroid_next_free(); if (index == -1) break; angle = rnd_int(0, 360 / 3) + (i * (360 / 3)); speed = rnd_int(1, 5) * 0.1f; rot_speed = rnd_int(0, 10) * DEG_TO_RAD(0.2f); x_off = rnd_int(-10, 10); y_off = rnd_int(-10, 10); asteroid_setup(index, FALSE, x + x_off, y + y_off, DEG_TO_RAD(angle), rot_speed, speed); } } void asteroid_update_all(float elapsed) { int i; VECTOR2F v; ASTEROID *a; for (i = 0; i < MAX_ASTEROIDS; ++i) { if (!asteroids[i].alive) continue; a = &asteroids[i]; a->position = vector2f_add(a->position, a->velocity); a->angle += a->rotationSpeed; if (a->angle >= RADIANS_360) a->angle -= RADIANS_360; if (a->angle < RADIANS_0) a->angle += RADIANS_360; wrap_screen(&a->position, a->object); if (game.mode == GAME_MODE_PLAY) { if (object_intersects(a->object, &a->position, player.object, &player.position)) { particle_spawn_explosion(player.position.x, player.position.y); player_kill(); } } } } // -------------------------------------------------------------------------- // BULLET management void bullet_reset_all(void) { memset(bullets, 0, sizeof(BULLET) * MAX_BULLETS); } int bullet_next_free(void) { int i; for (i = 0; i < MAX_BULLETS; ++i) { if (!bullets[i].alive) return i; } return -1; } void bullet_setup(int index, float x, float y, float angle, float speed) { bullets[index].alive = TRUE; bullets[index].position.x = x; bullets[index].position.y = y; bullets[index].object = bullet; point_on_circle(speed, angle, &bullets[index].velocity.x, &bullets[index].velocity.y); bullets[index].speed = speed; bullets[index].angle = angle; bullets[index].distance_left = MAX_BULLET_DISTANCE; } void bullet_destroy(int index) { bullets[index].alive = FALSE; } void bullet_update_all(float elapsed) { int i, j; BULLET *b; ASTEROID *a; for (i = 0; i < MAX_BULLETS; ++i) { if (!bullets[i].alive) continue; b = &bullets[i]; b->position = vector2f_add(b->position, b->velocity); b->distance_left -= b->speed; if (b->distance_left < 0.0f) { bullet_destroy(i); continue; } wrap_screen(&b->position, b->object); for (j = 0; j < MAX_ASTEROIDS; ++j) { if (!asteroids[j].alive) continue; a = &asteroids[j]; if (object_intersects(b->object, &b->position, a->object, &a->position)) { particle_spawn_explosion(a->position.x, a->position.y); if (a->large) { asteroid_spawn_broken_parts(a->position.x, a->position.y); player.score += 500; } else { player.score += 100; } asteroid_destroy(j); bullet_destroy(i); break; } } } } // -------------------------------------------------------------------------- // PARTICLE management void particle_reset_all(void) { memset(particles, 0, sizeof(PARTICLE) * MAX_PARTICLES); } int particle_next_free(void) { int i; for (i = 0; i < MAX_PARTICLES; ++i) { if (!particles[i].alive) return i; } return -1; } void particle_setup(int index, float x, float y, float angle, float speed, float distance) { particles[index].alive = TRUE; particles[index].position.x = x; particles[index].position.y = y; point_on_circle(speed, angle, &particles[index].velocity.x, &particles[index].velocity.y); particles[index].angle = angle; particles[index].speed = speed; particles[index].distance_left = distance; } void particle_destroy(int index) { particles[index].alive = FALSE; } void particle_update_all(float elapsed) { int i; PARTICLE *p; for (i = 0; i < MAX_PARTICLES; ++i) { if (!particles[i].alive) continue; p = &particles[i]; p->position = vector2f_add(p->position, p->velocity); p->distance_left -= p->speed; if (p->distance_left < 0.0f) { particle_destroy(i); continue; } wrap_screen(&p->position, NULL); } } void particle_spawn_explosion(float x, float y) { int index; float angle; for (angle = 0; angle <= RADIANS_360; angle += DEG_TO_RAD(30)) { index = particle_next_free(); if (index == -1) return; particle_setup(index, x, y, angle, 3.0f, 100.0f); } } // -------------------------------------------------------------------------- // PLAYER management void player_accelerate(void) { player.accel = THRUST; } void player_turn(float amount) { player.angle += amount; if (player.angle >= RADIANS_360) player.angle -= RADIANS_360; if (player.angle < RADIANS_0) player.angle += RADIANS_360; } void player_shoot(void) { int i; float speed; if (player.fire_cooldown > 0.0f) return; player.fire_cooldown = FIRE_COOLDOWN_TIME; i = bullet_next_free(); if (i == -1) return; speed = vector2f_length(player.velocity) + BULLET_SPEED; bullet_setup(i, player.position.x, player.position.y, player.angle, speed); } void player_spawn(void) { player.position = vector2f(160.0f, 100.0f); player.velocity = ZERO_VECTOR2F; player.angle = 0.0f; } void player_kill(void) { if (player.lives == 0) { game.mode = GAME_MODE_GAME_OVER; } else { player.lives--; game.mode = GAME_MODE_DEAD; game.dead_timer = 0; } } void player_update(float elapsed) { VECTOR2F v; point_on_circle(player.accel, player.angle, &v.x, &v.y); player.velocity = vector2f_add(player.velocity, v); player.position = vector2f_add(player.position, player.velocity); if (vector2f_length(player.velocity) > MAX_SPEED) player.velocity = vector2f_set_length(player.velocity, MAX_SPEED); player.accel = 0.0f; wrap_screen(&player.position, player.object); player.fire_cooldown -= elapsed; if (player.fire_cooldown < 0.0f) player.fire_cooldown = 0.0f; } // -------------------------------------------------------------------------- // rendering void draw_player(SURFACE *dest) { object_draw(dest, player.object, player.position.x, player.position.y, player.angle); } void draw_asteroids(SURFACE *dest) { int i; for (i = 0; i < MAX_ASTEROIDS; ++i) { if (!asteroids[i].alive) continue; object_draw(dest, asteroids[i].object, asteroids[i].position.x, asteroids[i].position.y, asteroids[i].angle); } } void draw_bullets(SURFACE *dest) { int i; for (i = 0; i < MAX_BULLETS; ++i) { if (!bullets[i].alive) continue; object_draw(dest, bullets[i].object, bullets[i].position.x, bullets[i].position.y, bullets[i].angle); } } void draw_particles(SURFACE *dest) { int i; for (i = 0; i < MAX_PARTICLES; ++i) { if (!particles[i].alive) continue; surface_pset(dest, particles[i].position.x, particles[i].position.y, 15); } } void draw_game_status(SURFACE *dest) { int seconds; seconds = ticks_to_seconds(game.time); surface_printf(dest, 140, 0, 15, "%2d:%02d", seconds / 60, seconds % 60); surface_printf(dest, 0, 0, 15, "Lives: %d", player.lives); surface_printf(dest, 320 - (8 * 7), 0, 15, "%7d", player.score); } void draw_game_title(SURFACE *dest) { surface_filled_rect(dest, 80, 88, 230, 112, 0); surface_rect(dest, 81, 89, 229, 111, 15); surface_text(dest, 120, 96, 15, "ASTEROIDS"); } void draw_game_dead(SURFACE *dest) { if (ticks_to_seconds(game.dead_timer) > DEAD_TIME) { surface_filled_rect(dest, 80, 88, 230, 112, 0); surface_rect(dest, 81, 89, 229, 111, 15); surface_text(dest, 120, 96, 15, "YOU DIED!"); } } void draw_game_over(SURFACE *dest) { if (ticks_to_seconds(game.dead_timer) > DEAD_TIME) { surface_filled_rect(dest, 80, 88, 230, 112, 0); surface_rect(dest, 81, 89, 229, 111, 15); surface_text(dest, 120, 96, 15, "GAME OVER"); } } // -------------------------------------------------------------------------- void game_init_level(int num_asteroids) { int i; asteroid_reset_all(); bullet_reset_all(); particle_reset_all(); player.lives = 3; player.score = 0; player.object = ship; player_spawn(); game.ticks = sys_ticks(); game.spawn_timer = 0; game.dead_timer = 0; game.time = 0; for (i = 0; i < num_asteroids; ++i) { asteroid_spawn(TRUE); } } void game_setup_title_screen(void) { game_init_level(10); game.mode = GAME_MODE_TITLE_SCREEN; } void game_title_screen(void) { if (keys[KEY_ENTER]) { game_init_level(3); game.mode = GAME_MODE_PLAY; } asteroid_update_all(game.frame_time); } void game_play(void) { if (keys[KEY_UP]) { player_accelerate(); } if (keys[KEY_LEFT]) { player_turn(-TURN_SPEED); } if (keys[KEY_RIGHT]) { player_turn(TURN_SPEED); } if (keys[KEY_SPACE]) { player_shoot(); } if (ticks_to_seconds(game.spawn_timer) > ASTEROID_SPAWN_TIME) { asteroid_spawn(FALSE); game.spawn_timer = 0; } player_update(game.frame_time); asteroid_update_all(game.frame_time); bullet_update_all(game.frame_time); particle_update_all(game.frame_time); } void game_dead(void) { asteroid_update_all(game.frame_time); bullet_update_all(game.frame_time); particle_update_all(game.frame_time); if (ticks_to_seconds(game.dead_timer) > DEAD_TIME) { if (keys[KEY_ENTER]) { game.mode = GAME_MODE_PLAY; player_spawn(); } } } void game_over(void) { asteroid_update_all(game.frame_time); bullet_update_all(game.frame_time); particle_update_all(game.frame_time); if (ticks_to_seconds(game.dead_timer) > DEAD_TIME) { if (keys[KEY_ENTER]) { game_setup_title_screen(); } } } void game_update_time(void) { int elapsed; game.last_ticks = game.ticks; game.ticks = sys_ticks(); elapsed = game.ticks - game.last_ticks; game.frame_time = ticks_to_seconds(elapsed); if (game.mode == GAME_MODE_PLAY || game.mode == GAME_MODE_DEAD) { game.time += elapsed; game.spawn_timer += elapsed; } if (game.mode == GAME_MODE_DEAD) game.dead_timer += elapsed; } int main(int argc, char** argv) { SURFACE *backbuffer; if (!dgl_init()) { printf("Failed to initialize DGL: %s\n", dgl_last_error_message()); return 1; } srandom(sys_clock()); backbuffer = surface_create(320, 200); object_init_all(); game_setup_title_screen(); while (!keys[KEY_ESC]) { game_update_time(); switch (game.mode) { case GAME_MODE_TITLE_SCREEN: game_title_screen(); break; case GAME_MODE_PLAY: game_play(); break; case GAME_MODE_DEAD: game_dead(); break; case GAME_MODE_GAME_OVER: game_over(); break; } surface_clear(backbuffer, 0); draw_asteroids(backbuffer); switch (game.mode) { case GAME_MODE_TITLE_SCREEN: draw_game_title(backbuffer); break; case GAME_MODE_PLAY: draw_bullets(backbuffer); draw_particles(backbuffer); draw_player(backbuffer); draw_game_status(backbuffer); break; case GAME_MODE_DEAD: draw_game_status(backbuffer); draw_bullets(backbuffer); draw_particles(backbuffer); draw_game_dead(backbuffer); break; case GAME_MODE_GAME_OVER: draw_game_status(backbuffer); draw_bullets(backbuffer); draw_particles(backbuffer); draw_game_over(backbuffer); break; } video_wait_vsync(); surface_copy(backbuffer, screen); if (keys[KEY_F1]) { pcx_save("screen.pcx", backbuffer, NULL); break; } } object_free_all(); if (!dgl_shutdown()) { printf("Failed to close DGL: %s\n", dgl_last_error_message()); return 1; } return 0; }