ex4.c (10784B)
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <time.h> 4 #include <unistd.h> 5 #include <string.h> 6 #include <math.h> 7 8 #include <X11/Xlib.h> 9 10 /* macros */ 11 #define INIT_WIDTH (250) 12 #define INIT_HEIGHT (200) 13 #define FPS (60) 14 #define SUB_TICK (4) 15 #define NUM_SQUARE (100) 16 #define max(a, b) ((a) > (b) ? (a) : (b)) 17 #define min(a, b) ((a) < (b) ? (a) : (b)) 18 19 #define COUNT_FPS 20 21 enum keys { 22 KEY_D, 23 KEY_S, 24 KEY_A, 25 KEY_W, 26 KEY_Q, 27 KEY_SPACE, 28 NUM_KEY, //number of keys in this enum 29 }; 30 enum key_state { 31 KEY_UP, 32 KEY_DOWN, 33 }; 34 enum next_menu { 35 START_MENU, 36 GAME_PLAY, 37 GAME_OVER, 38 QUIT, 39 }; 40 41 struct square { 42 float ppx, ppy; // previous position 43 float px, py; 44 float vx, vy; 45 int w, h; 46 }; 47 48 /* variables */ 49 Display *display; 50 Window window; 51 unsigned int win_width = INIT_WIDTH, win_height = INIT_HEIGHT; 52 GC gc, sgc[NUM_SQUARE]; 53 Atom wm_delete_window; 54 struct square square[NUM_SQUARE]; 55 int next_menu = START_MENU; 56 57 58 /* function prototypes */ 59 void setup(void); 60 void cleanup(void); 61 void start_menu(void); 62 void game_play(void); 63 void receive_events(int[]); 64 void handle_inputs(int[]); 65 void next_tick(struct square *, long); 66 int test_collision(struct square *, struct square *); 67 void handle_collision_mf(struct square *, struct square *); 68 void handle_collision(struct square *, struct square *); 69 void handle_collision_elastic(struct square *, struct square *); 70 void game_over(void); 71 72 73 void 74 setup(void) 75 { 76 if ((display = XOpenDisplay(NULL)) == NULL){ 77 fprintf(stderr, "ERROR: could not open display\n"); 78 exit(1); 79 } 80 window = XCreateSimpleWindow( 81 display, 82 XDefaultRootWindow(display), 83 0, 0, 84 win_width, win_height, 85 0, 0, 86 0); 87 XStoreName(display, window, "UNKO"); 88 gc = XCreateGC(display, window, 0, NULL); 89 XSetForeground(display, gc, 0x00FFFF); 90 for (int i = 0; i < NUM_SQUARE; i++){ 91 sgc[i] = XCreateGC(display, window, 0, NULL); 92 XSetForeground(display, sgc[i], 0x00FF00); 93 } 94 95 wm_delete_window = XInternAtom(display, 96 "WM_DELETE_WINDOW", False); 97 XSetWMProtocols(display, window, &wm_delete_window, 1); 98 99 XSelectInput(display, window, 100 ExposureMask | KeyPressMask | KeyReleaseMask); 101 102 XMapWindow(display, window); 103 } 104 105 void 106 start_menu(void) 107 { 108 XEvent event; 109 char *menu_char_q = "press q to quit."; 110 char *menu_char_s = "press <space> to start."; 111 112 XClearArea(display, window, 113 0, 0, // position 114 win_width, win_height, // width and height 115 False); 116 XDrawString(display, window, gc, 117 win_width/2 - strlen(menu_char_q)/2, win_height/2, 118 menu_char_q, strlen(menu_char_q)); 119 XDrawString(display, window, gc, 120 win_width/2 - strlen(menu_char_s)/2, win_height/2 + 20, 121 menu_char_s, strlen(menu_char_s)); 122 123 while (next_menu == START_MENU) { 124 XNextEvent(display, &event); 125 switch (event.type) { 126 case Expose: { 127 XDrawString(display, window, gc, 128 win_width/2 - strlen(menu_char_q)/2, 129 win_height/2, 130 menu_char_q, strlen(menu_char_q)); 131 XDrawString(display, window, gc, 132 win_width/2 - strlen(menu_char_s)/2, 133 win_height/2 + 20, 134 menu_char_s, strlen(menu_char_s)); 135 136 } break; 137 case KeyPress: { 138 switch (XLookupKeysym(&event.xkey, 0)) { 139 case 'q': 140 next_menu = QUIT; 141 break; 142 case ' ': 143 next_menu = GAME_PLAY; 144 break; 145 default: 146 break; 147 } 148 } break; 149 case ClientMessage: { 150 if ((Atom) event.xclient.data.l[0] == wm_delete_window) { 151 next_menu = QUIT; 152 } 153 } break; 154 default: 155 break; 156 } 157 } 158 } 159 160 void 161 receive_events(int key_state[]) 162 { 163 XEvent event; 164 XWindowAttributes wattr; 165 166 while (XPending(display) > 0) { 167 XNextEvent(display, &event); 168 switch (event.type) { 169 case Expose: { 170 XGetWindowAttributes(display, window, &wattr); 171 win_width = wattr.width; 172 win_height = wattr.height; 173 } break; 174 case KeyPress: { 175 switch (XLookupKeysym(&event.xkey, 0)) { 176 case 'q': 177 //next_menu = GAME_OVER; 178 key_state[KEY_Q] = KEY_DOWN; 179 break; 180 case 'd': 181 key_state[KEY_D] = KEY_DOWN; 182 break; 183 case 'a': 184 key_state[KEY_A] = KEY_DOWN; 185 break; 186 case 'w': 187 key_state[KEY_W] = KEY_DOWN; 188 break; 189 case 's': 190 key_state[KEY_S] = KEY_DOWN; 191 break; 192 default: 193 break; 194 } 195 } break; 196 case KeyRelease: { 197 switch (XLookupKeysym(&event.xkey, 0)) { 198 case 'q': 199 key_state[KEY_Q] = KEY_UP; 200 break; 201 case 'd': 202 key_state[KEY_D] = KEY_UP; 203 break; 204 case 'a': 205 key_state[KEY_A] = KEY_UP; 206 break; 207 case 'w': 208 key_state[KEY_W] = KEY_UP; 209 break; 210 case 's': 211 key_state[KEY_S] = KEY_UP; 212 break; 213 default: 214 break; 215 } 216 } break; 217 case ClientMessage: { 218 if ((Atom) event.xclient.data.l[0] == wm_delete_window) { 219 next_menu = QUIT; 220 } 221 } break; 222 default: 223 break; 224 } 225 } 226 } 227 228 void 229 handle_inputs(int key_state[]) 230 { 231 if (key_state[KEY_Q] == KEY_DOWN){ 232 next_menu = GAME_OVER; 233 return; 234 } 235 /* 236 square[0].vx = square[0].vy = 0; 237 if (key_state[KEY_D] == KEY_DOWN) 238 square[0].vx += 300; 239 if (key_state[KEY_A] == KEY_DOWN) 240 square[0].vx += -300; 241 if (key_state[KEY_S] == KEY_DOWN) 242 square[0].vy += 300; 243 if (key_state[KEY_W] == KEY_DOWN) 244 square[0].vy += -300; 245 */ 246 } 247 248 void 249 next_tick(struct square *s, long ndt) // nano second 250 { 251 s->ppx = s->px; 252 s->ppy = s->py; 253 s->px = s->px + s->vx * ndt / 1000 / 1000 / 1000; 254 s->py = s->py + s->vy * ndt / 1000 / 1000 / 1000; 255 256 // bind within the window 257 if (s->px < 0) { 258 s->px = 0; 259 s->vx *= -1; 260 } 261 if (win_width < s->px + s->w) { 262 s->px = win_width - s->w; 263 s->vx *= -1; 264 } 265 if (s->py < 0) { 266 s->py = 0; 267 s->vy *= -1; 268 } 269 if (win_height < s->py + s->h) { 270 s->py = win_height - s->h; 271 s->vy *= -1; 272 } 273 } 274 275 int 276 test_collision(struct square *s1, struct square* s2) 277 { 278 return s1->px < s2->px + s2->w && s2->px < s1->px + s1->w && 279 s2->py < s1->py + s1->h && s1->py < s2->py + s2->h; 280 } 281 282 /* 283 * Handle collision of a moving square against fixed square 284 */ 285 void 286 handle_collision_mf(struct square *sm, struct square *sf) 287 { 288 if (!test_collision(sm, sf)) 289 return; 290 if (sm->ppx + sm->w <= sf->ppx && sf->px < sm->px + sm->w) 291 // collisioin from left to right 292 sm->px = sf->px - sm->w; 293 if (sf->ppx + sf->w <= sm->ppx && sm->px < sf->px + sf->w) 294 // collision from right to left 295 sm->px = sf->px + sf->w; 296 297 if (sm->ppy + sm->h <= sf->ppy && sf->py < sm->py + sm->h) 298 // collision from up to down 299 sm->py = sf->py - sm->h; 300 if (sf->ppy + sf->h <= sm->ppy && sm->py < sf->py + sf->h) 301 // collision from dohn to up 302 sm->py = sf->py + sf->h; 303 } 304 305 /* 306 * Handle collision of a moving square against another moving square 307 */ 308 void 309 handle_collision_mm(struct square *s1, struct square *s2) 310 { 311 if (!test_collision(s1, s2)) 312 return; 313 314 float lapx = min(s1->px + s1->w, s2->px + s2->w) - max(s1->px, s2->px); 315 float lapy = min(s1->py + s1->h, s2->py + s2->h) - max(s1->py, s2->py); 316 float rel_vx = max(s1->vx - s2->vx, s2->vx - s1->vx); 317 float rel_vy = max(s1->vy - s2->vy, s2->vy - s1->vy); 318 319 if (lapx / rel_vx < lapy / rel_vy) { 320 if (s1->px + s1->w < s2->px + s2->w / 2) { 321 s1->px -= lapx / 2; 322 s2->px += lapx / 2; 323 } else { 324 s1->px += lapx / 2; 325 s2->px -= lapx / 2; 326 } 327 } else { 328 if (s1->py + s1->h < s2->py + s2->h / 2) { 329 s1->py -= lapy / 2; 330 s2->py += lapy / 2; 331 } else { 332 s1->py += lapy / 2; 333 s2->py -= lapy / 2; 334 } 335 } 336 } 337 338 void 339 handle_collision_elastic(struct square *s1, struct square *s2) 340 { 341 if(!test_collision(s1, s2)) 342 return; 343 344 float v1, v2; 345 float m1 = s1->w * s1->h; 346 float m2 = s2->w * s2->h; 347 348 float lapx = min(s1->px + s1->w, s2->px + s2->w) - max(s1->px, s2->px); 349 float lapy = min(s1->py + s1->h, s2->py + s2->h) - max(s1->py, s2->py); 350 351 if (lapx < lapy) { 352 v1 = s1->vx; 353 v2 = s2->vx; 354 s1->vx = 2*m2/(m1+m2)*v2 + (m1-m2)/(m1+m2)*v1; 355 s2->vx = 2*m1/(m1+m2)*v1 + (m2-m1)/(m1+m2)*v2; 356 } else { 357 v1 = s1->vy; 358 v2 = s2->vy; 359 s1->vy = 2*m2/(m1+m2)*v2 + (m1-m2)/(m1+m2)*v1; 360 s2->vy = 2*m1/(m1+m2)*v1 + (m2-m1)/(m1+m2)*v2; 361 } 362 363 handle_collision_mm(s1, s2); 364 } 365 366 void 367 game_play(void) 368 { 369 int key_state[NUM_KEY]; 370 long t0, t1, dt; 371 #ifdef COUNT_FPS 372 int fps_count = 0; 373 #endif 374 struct timespec ts; 375 376 for(int i = 0; i < NUM_SQUARE; i++){ 377 square[i].ppx = square[i].px = (float)rand() * win_width / RAND_MAX; 378 square[i].ppy = square[i].py = (float)rand() * win_height / RAND_MAX; 379 square[i].vx = (float)rand() * 100 / RAND_MAX - 50; 380 square[i].vy = (float)rand() * 100 / RAND_MAX - 50; 381 square[i].w = square[i].h = (float)rand() * 10 / RAND_MAX; 382 } 383 384 385 while (next_menu == GAME_PLAY){ 386 clock_gettime(CLOCK_MONOTONIC, &ts); 387 t0 = ts.tv_nsec; 388 receive_events(key_state); 389 handle_inputs(key_state); 390 391 392 clock_gettime(CLOCK_MONOTONIC, &ts); 393 t0 = ts.tv_nsec; 394 395 int collision[NUM_SQUARE] = {0}; 396 for (int j = 0; j < SUB_TICK; j++) { 397 for (int i = 0; i < NUM_SQUARE; i++) 398 next_tick(&square[i], 1000 * 1000 * 1000 / FPS / SUB_TICK); 399 400 for (int i = 0; i < NUM_SQUARE; i++) 401 for (int j = i + 1; j < NUM_SQUARE; j++) { 402 handle_collision_elastic(&square[i], &square[j]); 403 if (test_collision(&square[i], &square[j])) 404 collision[i] = collision[j] = 1; 405 } 406 } 407 for (int i = 0; i < NUM_SQUARE; i++) 408 if (collision[i] == 1) 409 XSetForeground(display, sgc[i], 0xFFFF00); 410 else 411 XSetForeground(display, sgc[i], 0xFF << i % 3 * 8); 412 413 // fix fps 414 // TODO: This method create some strange stripe when 415 // rendered in 60fps on a 60fps monitor. 416 dt = 0; 417 while (dt < 1.0 * 1000 * 1000 * 1000 / FPS){ 418 clock_gettime(CLOCK_MONOTONIC, &ts); 419 t1 = ts.tv_nsec; 420 dt = t1 > t0 ? t1 - t0 : t1 - t0 + 1000 * 1000 * 1000; 421 } 422 #ifdef COUNT_FPS 423 // count fps. 424 fps_count++; 425 if (t1 < t0){ 426 printf("fps: %u\n", fps_count); 427 fps_count = 0; 428 } 429 #endif 430 431 XClearArea(display, window, 432 0, 0, // position 433 win_width, win_height, // width and height 434 False); 435 for (int i = 0; i < NUM_SQUARE; i++) { 436 XDrawRectangle(display, window, sgc[i], 437 square[i].px, square[i].py, // position 438 square[i].w, square[i].h); // width and height 439 } 440 } 441 XSetForeground(display, gc, 0x00FFFF); 442 } 443 444 void 445 game_over(void) 446 { 447 char *menu_char = "GAME OVER"; 448 449 XClearArea(display, window, 450 0, 0, // position 451 win_width, win_height, // width and height 452 False); 453 XDrawString(display, window, gc, 454 win_width/2 - strlen(menu_char)/2, win_height/2, 455 menu_char, strlen(menu_char)); 456 XFlush(display); 457 458 sleep(1); 459 next_menu = START_MENU; 460 } 461 462 void 463 cleanup(void) 464 { 465 XCloseDisplay(display); 466 } 467 468 int 469 main(void) 470 { 471 setup(); 472 while (next_menu != QUIT){ 473 switch (next_menu){ 474 case START_MENU: 475 start_menu(); 476 break; 477 case GAME_PLAY: 478 game_play(); 479 break; 480 case GAME_OVER: 481 game_over(); 482 break; 483 default: 484 break; 485 } 486 } 487 488 cleanup(); 489 return 0; 490 }