From 1601d7c3681403a2636553761b3d44c62d085531 Mon Sep 17 00:00:00 2001 From: Krzysztof kuhy Rudnicki Date: Sun, 24 Aug 2025 13:14:47 +0200 Subject: [PATCH] feat: make the game have any sense at al --- C/fps/main.c | 177 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 142 insertions(+), 35 deletions(-) diff --git a/C/fps/main.c b/C/fps/main.c index 957c357..ecbe6b6 100644 --- a/C/fps/main.c +++ b/C/fps/main.c @@ -1,7 +1,8 @@ // Simple FPS demo using FreeGLUT + legacy OpenGL (compat profile) // - Move: WASD, Shift to sprint, Space to shoot, Esc to quit // - Look: mouse -// - Target: red cube; shoot to score and respawn target +// - Targets: red cubes move toward you; shoot them before they reach you +// - Game over when a target reaches you; final score shown; press R to restart #include #include @@ -10,6 +11,7 @@ #include #include #include +#include #ifndef M_PI #define M_PI 3.14159265358979323846 @@ -47,10 +49,17 @@ static float g_move_speed = 4.0f; // m/s static float g_sprint_mul = 1.8f; static float g_mouse_sens = 0.12f; // deg per pixel -typedef struct { vec3 pos; float radius; } Target; -static Target g_target; +typedef struct { vec3 pos; float radius; float speed; } Target; +static const int MAX_TARGETS = 128; +static Target g_targets[128]; +static int g_target_count = 0; +static float g_spawn_timer = 0.0f; +static float g_spawn_interval = 1.2f; // seconds static int g_score = 0; +typedef enum { GAME_RUNNING = 0, GAME_OVER = 1 } GameState; +static GameState g_state = GAME_RUNNING; + // Bullet visualization static float g_bullet_t = 0.0f; // seconds left to show bullet line static vec3 g_bullet_origin, g_bullet_dir; @@ -75,12 +84,36 @@ static vec3 cam_right() return v3_norm(v3_cross(cam_front(), v3(0,1,0))); } -static void respawn_target() +static float frand(float a, float b) { return a + (b-a) * (rand()/(float)RAND_MAX); } + +static void clear_targets() { g_target_count = 0; } + +static void spawn_target() { - float x = ((rand()/(float)RAND_MAX) * 24.0f) - 12.0f; // [-12,12] - float z = ((rand()/(float)RAND_MAX) * 24.0f) - 12.0f; // [-12,12] - g_target.pos = v3(x, 0.5f, z); - g_target.radius = 0.6f; // fits a 1.0 cube roughly + if (g_target_count >= MAX_TARGETS) return; + float radius_spawn = frand(22.0f, 34.0f); + float ang = frand(0.0f, 2.0f*(float)M_PI); + vec3 p = v3(cosf(ang)*radius_spawn, 0.5f, sinf(ang)*radius_spawn); + Target t; + t.pos = p; + t.radius = 0.6f; + t.speed = frand(1.4f, 3.2f); + g_targets[g_target_count++] = t; +} + +static void reset_game() +{ + g_score = 0; + g_spawn_timer = 0.0f; + g_spawn_interval = 1.2f; + clear_targets(); + // spawn a few to start + for (int i = 0; i < 4; ++i) spawn_target(); + g_state = GAME_RUNNING; + if (g_captured_mouse) { + g_ignore_next_passive = true; + glutWarpPointer(g_win_w/2, g_win_h/2); + } } // Ray-sphere intersection: returns t >= 0 for first hit, or -1 if miss @@ -97,18 +130,23 @@ static float ray_sphere(vec3 ro, vec3 rd, vec3 c, float r) static void shoot() { + if (g_state != GAME_RUNNING) return; vec3 dir = cam_front(); - float t = ray_sphere(g_cam_pos, dir, g_target.pos, g_target.radius); + float best_t = 1e9f; + int best_i = -1; + for (int i = 0; i < g_target_count; ++i) { + float t = ray_sphere(g_cam_pos, dir, g_targets[i].pos, g_targets[i].radius); + if (t >= 0.0f && t < best_t) { best_t = t; best_i = i; } + } g_bullet_origin = g_cam_pos; g_bullet_dir = dir; g_bullet_t = 0.08f; // show for ~80ms - if (t >= 0.0f && t < 100.0f) { + if (best_i >= 0) { + // remove hit target (swap remove) + g_targets[best_i] = g_targets[g_target_count-1]; + g_target_count--; g_score++; - char title[128]; - snprintf(title, sizeof(title), "FPS Demo | Score: %d", g_score); - glutSetWindowTitle(title); - respawn_target(); } } @@ -183,11 +221,13 @@ static void display() // Ground grid draw_grid(40.0f, 1.0f); - // Target cube - glPushMatrix(); - glTranslatef(g_target.pos.x, g_target.pos.y, g_target.pos.z); - draw_cube(); - glPopMatrix(); + // Target cubes + for (int i = 0; i < g_target_count; ++i) { + glPushMatrix(); + glTranslatef(g_targets[i].pos.x, g_targets[i].pos.y, g_targets[i].pos.z); + draw_cube(); + glPopMatrix(); + } // Bullet line if (g_bullet_t > 0.0f) { @@ -200,8 +240,42 @@ static void display() glEnd(); } - // Crosshair overlay - draw_crosshair(); + // Crosshair overlay (only during gameplay) + if (g_state == GAME_RUNNING) { + draw_crosshair(); + } + + // Game over overlay text + if (g_state == GAME_OVER) { + // Switch to 2D for text + glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); + gluOrtho2D(0, g_win_w, g_win_h, 0); + glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); + glDisable(GL_DEPTH_TEST); + + const char* line1 = "GAME OVER"; + char line2[64]; snprintf(line2, sizeof(line2), "Score: %d", g_score); + const char* line3 = "Press R to restart or Esc to quit"; + + void* font = GLUT_BITMAP_HELVETICA_18; + int cx = g_win_w/2; int cy = g_win_h/2; + glColor3f(1,1,1); + // naive center: estimate width by char count * 9 px + int w1 = (int)(9 * strlen(line1)); + int w2 = (int)(9 * strlen(line2)); + int w3 = (int)(9 * strlen(line3)); + + glRasterPos2i(cx - w1/2, cy - 30); + for (const char* p=line1; *p; ++p) glutBitmapCharacter(font, *p); + glRasterPos2i(cx - w2/2, cy - 8); + for (const char* p=line2; *p; ++p) glutBitmapCharacter(font, *p); + glRasterPos2i(cx - w3/2, cy + 18); + for (const char* p=line3; *p; ++p) glutBitmapCharacter(font, *p); + + glEnable(GL_DEPTH_TEST); + glMatrixMode(GL_MODELVIEW); glPopMatrix(); + glMatrixMode(GL_PROJECTION); glPopMatrix(); + } glutSwapBuffers(); } @@ -209,17 +283,44 @@ static void display() // -------- Update/Input -------- static void update(float dt) { - float speed = g_move_speed * ((g_keys['\t'] || g_keys['Q'] || g_keys['q']) ? g_sprint_mul : 1.0f); // Tab/Q to sprint - vec3 f = cam_front(); f.y = 0.0f; f = v3_norm(f); - vec3 r = cam_right(); r.y = 0.0f; r = v3_norm(r); + if (g_state == GAME_RUNNING) { + float speed = g_move_speed * ((g_keys['\t'] || g_keys['Q'] || g_keys['q']) ? g_sprint_mul : 1.0f); // Tab/Q to sprint + vec3 f = cam_front(); f.y = 0.0f; f = v3_norm(f); + vec3 r = cam_right(); r.y = 0.0f; r = v3_norm(r); - if (g_keys['W'] || g_keys['w']) g_cam_pos = v3_add(g_cam_pos, v3_scale(f, speed*dt)); - if (g_keys['S'] || g_keys['s']) g_cam_pos = v3_sub(g_cam_pos, v3_scale(f, speed*dt)); - if (g_keys['A'] || g_keys['a']) g_cam_pos = v3_sub(g_cam_pos, v3_scale(r, speed*dt)); - if (g_keys['D'] || g_keys['d']) g_cam_pos = v3_add(g_cam_pos, v3_scale(r, speed*dt)); + if (g_keys['W'] || g_keys['w']) g_cam_pos = v3_add(g_cam_pos, v3_scale(f, speed*dt)); + if (g_keys['S'] || g_keys['s']) g_cam_pos = v3_sub(g_cam_pos, v3_scale(f, speed*dt)); + if (g_keys['A'] || g_keys['a']) g_cam_pos = v3_sub(g_cam_pos, v3_scale(r, speed*dt)); + if (g_keys['D'] || g_keys['d']) g_cam_pos = v3_add(g_cam_pos, v3_scale(r, speed*dt)); - // Keep feet on ground - g_cam_pos.y = 1.6f; + // Keep feet on ground + g_cam_pos.y = 1.6f; + + // Move targets toward player and check collision + for (int i = 0; i < g_target_count; ++i) { + vec3 to_player = v3_sub(g_cam_pos, g_targets[i].pos); + to_player.y = 0.0f; + vec3 dir = v3_norm(to_player); + g_targets[i].pos = v3_add(g_targets[i].pos, v3_scale(dir, g_targets[i].speed * dt)); + g_targets[i].pos.y = 0.5f; + + float dist2 = to_player.x*to_player.x + to_player.z*to_player.z; + float reach = (0.6f + g_targets[i].radius); + if (dist2 <= reach*reach) { + g_state = GAME_OVER; + glutSetCursor(GLUT_CURSOR_LEFT_ARROW); + break; + } + } + + // Spawn new targets over time (slightly accelerate spawn rate) + g_spawn_timer += dt; + if (g_spawn_timer >= g_spawn_interval) { + g_spawn_timer = 0.0f; + spawn_target(); + g_spawn_interval = fmaxf(0.4f, g_spawn_interval * 0.98f); + } + } if (g_bullet_t > 0.0f) { g_bullet_t -= dt; @@ -248,7 +349,7 @@ static void reshape(int w, int h) gluPerspective(75.0, (double)g_win_w/(double)g_win_h, 0.05, 500.0); glMatrixMode(GL_MODELVIEW); - if (g_captured_mouse) { + if (g_captured_mouse && g_state == GAME_RUNNING) { g_ignore_next_passive = true; glutWarpPointer(g_win_w/2, g_win_h/2); } @@ -265,10 +366,15 @@ static void keyboard_down(unsigned char key, int x, int y) } else if (key == 'm' || key == 'M') { g_captured_mouse = !g_captured_mouse; glutSetCursor(g_captured_mouse ? GLUT_CURSOR_NONE : GLUT_CURSOR_LEFT_ARROW); - if (g_captured_mouse) { + if (g_captured_mouse && g_state == GAME_RUNNING) { g_ignore_next_passive = true; glutWarpPointer(g_win_w/2, g_win_h/2); } + } else if (key == 'r' || key == 'R') { + if (g_state == GAME_OVER) { + glutSetCursor(g_captured_mouse ? GLUT_CURSOR_NONE : GLUT_CURSOR_LEFT_ARROW); + reset_game(); + } } } @@ -288,7 +394,7 @@ static void mouse_button(int button, int state, int x, int y) static void passive_motion(int x, int y) { - if (!g_captured_mouse) return; + if (!g_captured_mouse || g_state != GAME_RUNNING) return; if (g_ignore_next_passive) { // ignore event caused by warp g_ignore_next_passive = false; @@ -320,12 +426,11 @@ int main(int argc, char** argv) { (void)argv; srand((unsigned)time(NULL)); - respawn_target(); glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); glutInitWindowSize(g_win_w, g_win_h); - glutCreateWindow("FPS Demo | Score: 0"); + glutCreateWindow("FPS Demo"); init_gl(); @@ -342,6 +447,8 @@ int main(int argc, char** argv) g_ignore_next_passive = true; glutWarpPointer(g_win_w/2, g_win_h/2); + reset_game(); + glutMainLoop(); return 0; }