ex5.c (13324B)
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_CIRCLE (50) 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 rect { 42 float ppx, ppy; // previous position 43 float px, py; // top left corner 44 float vx, vy; 45 int w, h; 46 int m; 47 }; 48 49 struct circle { 50 float ppx, ppy; 51 float px, py; 52 float vx, vy; 53 int r; 54 int m; 55 }; 56 57 /* variables */ 58 Display *display; 59 Window window; 60 unsigned int win_width = INIT_WIDTH, win_height = INIT_HEIGHT; 61 GC gc, sgc[NUM_CIRCLE]; 62 Atom wm_delete_window; 63 struct circle circle[NUM_CIRCLE]; 64 int next_menu = START_MENU; 65 66 67 /* function prototypes */ 68 void setup(void); 69 void cleanup(void); 70 void start_menu(void); 71 void game_play(void); 72 void receive_events(int[]); 73 void handle_inputs(int[]); 74 void rect_next_tick(struct rect *, long); 75 int rect_test_collision(struct rect *, struct rect *); 76 void rect_handle_collision_mf(struct rect *, struct rect *); 77 void rect_handle_collision(struct rect *, struct rect *); 78 void rect_handle_collision_elastic(struct rect *, struct rect *); 79 void circle_next_tick(struct circle *, long); 80 int circle_test_collision(struct circle *, struct circle *); 81 void game_over(void); 82 83 84 void 85 setup(void) 86 { 87 if ((display = XOpenDisplay(NULL)) == NULL){ 88 fprintf(stderr, "ERROR: could not open display\n"); 89 exit(1); 90 } 91 window = XCreateSimpleWindow( 92 display, 93 XDefaultRootWindow(display), 94 0, 0, 95 win_width, win_height, 96 0, 0, 97 0); 98 XStoreName(display, window, "UNKO"); 99 gc = XCreateGC(display, window, 0, NULL); 100 XSetForeground(display, gc, 0x00FFFF); 101 for (int i = 0; i < NUM_CIRCLE; i++){ 102 sgc[i] = XCreateGC(display, window, 0, NULL); 103 XSetForeground(display, sgc[i], 0x00FF00); 104 } 105 106 wm_delete_window = XInternAtom(display, 107 "WM_DELETE_WINDOW", False); 108 XSetWMProtocols(display, window, &wm_delete_window, 1); 109 110 XSelectInput(display, window, 111 ExposureMask | KeyPressMask | KeyReleaseMask); 112 113 XMapWindow(display, window); 114 } 115 116 void 117 start_menu(void) 118 { 119 XEvent event; 120 char *menu_char_q = "press q to quit."; 121 char *menu_char_s = "press <space> to start."; 122 123 XClearArea(display, window, 124 0, 0, // position 125 win_width, win_height, // width and height 126 False); 127 XDrawString(display, window, gc, 128 win_width/2 - strlen(menu_char_q)/2, win_height/2, 129 menu_char_q, strlen(menu_char_q)); 130 XDrawString(display, window, gc, 131 win_width/2 - strlen(menu_char_s)/2, win_height/2 + 20, 132 menu_char_s, strlen(menu_char_s)); 133 134 while (next_menu == START_MENU) { 135 XNextEvent(display, &event); 136 switch (event.type) { 137 case Expose: { 138 XDrawString(display, window, gc, 139 win_width/2 - strlen(menu_char_q)/2, 140 win_height/2, 141 menu_char_q, strlen(menu_char_q)); 142 XDrawString(display, window, gc, 143 win_width/2 - strlen(menu_char_s)/2, 144 win_height/2 + 20, 145 menu_char_s, strlen(menu_char_s)); 146 147 } break; 148 case KeyPress: { 149 switch (XLookupKeysym(&event.xkey, 0)) { 150 case 'q': 151 next_menu = QUIT; 152 break; 153 case ' ': 154 next_menu = GAME_PLAY; 155 break; 156 default: 157 break; 158 } 159 } break; 160 case ClientMessage: { 161 if ((Atom) event.xclient.data.l[0] == wm_delete_window) { 162 next_menu = QUIT; 163 } 164 } break; 165 default: 166 break; 167 } 168 } 169 } 170 171 void 172 receive_events(int key_state[]) 173 { 174 XEvent event; 175 XWindowAttributes wattr; 176 177 while (XPending(display) > 0) { 178 XNextEvent(display, &event); 179 switch (event.type) { 180 case Expose: { 181 XGetWindowAttributes(display, window, &wattr); 182 win_width = wattr.width; 183 win_height = wattr.height; 184 } break; 185 case KeyPress: { 186 switch (XLookupKeysym(&event.xkey, 0)) { 187 case 'q': 188 //next_menu = GAME_OVER; 189 key_state[KEY_Q] = KEY_DOWN; 190 break; 191 case 'd': 192 key_state[KEY_D] = KEY_DOWN; 193 break; 194 case 'a': 195 key_state[KEY_A] = KEY_DOWN; 196 break; 197 case 'w': 198 key_state[KEY_W] = KEY_DOWN; 199 break; 200 case 's': 201 key_state[KEY_S] = KEY_DOWN; 202 break; 203 default: 204 break; 205 } 206 } break; 207 case KeyRelease: { 208 switch (XLookupKeysym(&event.xkey, 0)) { 209 case 'q': 210 key_state[KEY_Q] = KEY_UP; 211 break; 212 case 'd': 213 key_state[KEY_D] = KEY_UP; 214 break; 215 case 'a': 216 key_state[KEY_A] = KEY_UP; 217 break; 218 case 'w': 219 key_state[KEY_W] = KEY_UP; 220 break; 221 case 's': 222 key_state[KEY_S] = KEY_UP; 223 break; 224 default: 225 break; 226 } 227 } break; 228 case ClientMessage: { 229 if ((Atom) event.xclient.data.l[0] == wm_delete_window) { 230 next_menu = QUIT; 231 } 232 } break; 233 default: 234 break; 235 } 236 } 237 } 238 239 void 240 handle_inputs(int key_state[]) 241 { 242 if (key_state[KEY_Q] == KEY_DOWN){ 243 next_menu = GAME_OVER; 244 return; 245 } 246 /* 247 circle[0].vx = circle[0].vy = 0; 248 if (key_state[KEY_D] == KEY_DOWN) 249 circle[0].vx += 300; 250 if (key_state[KEY_A] == KEY_DOWN) 251 circle[0].vx += -300; 252 if (key_state[KEY_S] == KEY_DOWN) 253 circle[0].vy += 300; 254 if (key_state[KEY_W] == KEY_DOWN) 255 circle[0].vy += -300; 256 */ 257 } 258 259 void 260 rect_next_tick(struct rect *s, long ndt) // nano second 261 { 262 s->ppx = s->px; 263 s->ppy = s->py; 264 s->px = s->px + s->vx * ndt / 1000 / 1000 / 1000; 265 s->py = s->py + s->vy * ndt / 1000 / 1000 / 1000; 266 267 // bind within the window 268 if (s->px < 0) { 269 s->px = 0; 270 s->vx *= -1; 271 } 272 if (win_width < s->px + s->w) { 273 s->px = win_width - s->w; 274 s->vx *= -1; 275 } 276 if (s->py < 0) { 277 s->py = 0; 278 s->vy *= -1; 279 } 280 if (win_height < s->py + s->h) { 281 s->py = win_height - s->h; 282 s->vy *= -1; 283 } 284 } 285 286 void 287 circle_next_tick(struct circle *c, long ndt) 288 { 289 c->ppx = c->px; 290 c->ppy = c->py; 291 c->px = c->px + c->vx * ndt / 1000 / 1000 / 1000; 292 c->py = c->py + c->vy * ndt / 1000 / 1000 / 1000; 293 294 // bind within the window 295 if (c->px - c->r < 0) { 296 c->px = c->r; 297 c->vx *= -1; 298 } 299 if (win_width < c->px + c->r) { 300 c->px = win_width - c->r; 301 c->vx *= -1; 302 } 303 if (c->py - c->r < 0) { 304 c->py = c->r; 305 c->vy *= -1; 306 } 307 if (win_height < c->py + c->r) { 308 c->py = win_height - c->r; 309 c->vy *= -1; 310 } 311 } 312 313 int 314 rect_test_collision(struct rect *s1, struct rect *s2) 315 { 316 return s1->px < s2->px + s2->w && s2->px < s1->px + s1->w && 317 s2->py < s1->py + s1->h && s1->py < s2->py + s2->h; 318 } 319 320 int 321 circle_test_collision(struct circle *c1, struct circle *c2) 322 { 323 return (c1->px - c2->px) * (c1->px - c2->px) + 324 (c1->py - c2->py) * (c1->py - c2->py) < 325 (c1->r + c2->r) * (c1->r + c2->r); 326 } 327 328 /* 329 * Handle collision of a moving rect against fixed rect 330 */ 331 void 332 rect_handle_collision_mf(struct rect *sm, struct rect *sf) 333 { 334 if (!rect_test_collision(sm, sf)) 335 return; 336 if (sm->ppx + sm->w <= sf->ppx && sf->px < sm->px + sm->w) 337 // collisioin from left to right 338 sm->px = sf->px - sm->w; 339 if (sf->ppx + sf->w <= sm->ppx && sm->px < sf->px + sf->w) 340 // collision from right to left 341 sm->px = sf->px + sf->w; 342 343 if (sm->ppy + sm->h <= sf->ppy && sf->py < sm->py + sm->h) 344 // collision from up to down 345 sm->py = sf->py - sm->h; 346 if (sf->ppy + sf->h <= sm->ppy && sm->py < sf->py + sf->h) 347 // collision from dohn to up 348 sm->py = sf->py + sf->h; 349 } 350 351 /* 352 * Handle collision of a moving rect against another moving rect 353 */ 354 void 355 rect_handle_collision_mm(struct rect *s1, struct rect *s2) 356 { 357 if (!rect_test_collision(s1, s2)) 358 return; 359 360 float lapx = min(s1->px + s1->w, s2->px + s2->w) - max(s1->px, s2->px); 361 float lapy = min(s1->py + s1->h, s2->py + s2->h) - max(s1->py, s2->py); 362 363 if (lapx < lapy) { 364 if (s1->px + s1->w < s2->px + s2->w / 2) { 365 s1->px -= lapx / 2; 366 s2->px += lapx / 2; 367 } else { 368 s1->px += lapx / 2; 369 s2->px -= lapx / 2; 370 } 371 } else { 372 if (s1->py + s1->h < s2->py + s2->h / 2) { 373 s1->py -= lapy / 2; 374 s2->py += lapy / 2; 375 } else { 376 s1->py += lapy / 2; 377 s2->py -= lapy / 2; 378 } 379 } 380 } 381 382 void 383 circle_handle_collision_mm(struct circle *c1, struct circle *c2) 384 { 385 if (!circle_test_collision(c1, c2)) 386 return; 387 388 float col_px = c2->px - c1->px; 389 float col_py = c2->py - c1->py; 390 float col_pr = sqrtf(col_px * col_px + col_py * col_py); 391 col_px /= col_pr; 392 col_py /= col_pr; 393 394 c1->px = c1->px - col_px / 2; 395 c1->py = c1->py - col_py / 2; 396 c2->px = c2->px + col_px / 2; 397 c2->py = c2->py + col_py / 2; 398 } 399 400 void 401 rect_handle_collision_elastic(struct rect *s1, struct rect *s2) 402 { 403 if(!rect_test_collision(s1, s2)) 404 return; 405 406 float v1, v2; 407 float m1 = s1->m; 408 float m2 = s2->m; 409 410 float lapx = min(s1->px + s1->w, s2->px + s2->w) - max(s1->px, s2->px); 411 float lapy = min(s1->py + s1->h, s2->py + s2->h) - max(s1->py, s2->py); 412 413 if (lapx < lapy) { 414 v1 = s1->vx; 415 v2 = s2->vx; 416 s1->vx = 2*m2/(m1+m2)*v2 + (m1-m2)/(m1+m2)*v1; 417 s2->vx = 2*m1/(m1+m2)*v1 + (m2-m1)/(m1+m2)*v2; 418 } else { 419 v1 = s1->vy; 420 v2 = s2->vy; 421 s1->vy = 2*m2/(m1+m2)*v2 + (m1-m2)/(m1+m2)*v1; 422 s2->vy = 2*m1/(m1+m2)*v1 + (m2-m1)/(m1+m2)*v2; 423 } 424 425 rect_handle_collision_mm(s1, s2); 426 } 427 428 void 429 circle_handle_collision_elastic(struct circle *c1, struct circle *c2) 430 { 431 if(!circle_test_collision(c1, c2)) 432 return; 433 434 float col_px = c2->px - c1->px; 435 float col_py = c2->py - c1->py; 436 float col_pr = sqrtf(col_px * col_px + col_py * col_py); 437 col_px /= col_pr; 438 col_py /= col_pr; 439 float nor_px = col_py; 440 float nor_py = -col_px; 441 442 float m1 = c1->m; 443 float m2 = c2->m; 444 445 float col_1v = c1->vx * col_px + c1->vy * col_py; 446 float col_2v = c2->vx * col_px + c2->vy * col_py; 447 448 float col_1vxn = (2*m2/(m1+m2)*col_2v + (m1-m2)/(m1+m2)*col_1v) * col_px; 449 float col_1vyn = (2*m2/(m1+m2)*col_2v + (m1-m2)/(m1+m2)*col_1v) * col_py; 450 float col_2vxn = (2*m1/(m1+m2)*col_1v + (m2-m1)/(m1+m2)*col_2v) * col_px; 451 float col_2vyn = (2*m1/(m1+m2)*col_1v + (m2-m1)/(m1+m2)*col_2v) * col_py; 452 453 float nor_1vx = nor_px * (c1->vx * nor_px + c1->vy * nor_py); 454 float nor_1vy = nor_py * (c1->vx * nor_px + c1->vy * nor_py); 455 float nor_2vx = nor_px * (c2->vx * nor_px + c2->vy * nor_py); 456 float nor_2vy = nor_py * (c2->vx * nor_px + c2->vy * nor_py); 457 458 c1->vx = col_1vxn + nor_1vx; 459 c1->vy = col_1vyn + nor_1vy; 460 c2->vx = col_2vxn + nor_2vx; 461 c2->vy = col_2vyn + nor_2vy; 462 463 circle_handle_collision_mm(c1, c2); 464 } 465 void 466 game_play(void) 467 { 468 int key_state[NUM_KEY]; 469 long t0, t1, dt; 470 #ifdef COUNT_FPS 471 int fps_count = 0; 472 #endif 473 struct timespec ts; 474 475 for(int i = 0; i < NUM_CIRCLE; i++){ 476 circle[i].ppx = circle[i].px = rand() * (float)win_width / (float)RAND_MAX; 477 circle[i].ppy = circle[i].py = rand() * (float)win_height / (float)RAND_MAX; 478 circle[i].vx = rand() * 100.0 / (float)RAND_MAX - 50; 479 circle[i].vy = rand() * 100.0 / (float)RAND_MAX - 50; 480 circle[i].r = rand() * 10.0 / (float)RAND_MAX; 481 circle[i].m = circle[i].r * circle[i].r; 482 } 483 484 while (next_menu == GAME_PLAY){ 485 clock_gettime(CLOCK_MONOTONIC, &ts); 486 t0 = ts.tv_nsec; 487 receive_events(key_state); 488 handle_inputs(key_state); 489 490 491 clock_gettime(CLOCK_MONOTONIC, &ts); 492 t0 = ts.tv_nsec; 493 494 int collision[NUM_CIRCLE] = {0}; 495 for (int k = 0; k < SUB_TICK; k++) { 496 for (int i = 0; i < NUM_CIRCLE; i++) 497 circle_next_tick(&circle[i], 1000 * 1000 * 1000 / FPS / SUB_TICK); 498 499 for (int i = 0; i < NUM_CIRCLE; i++) 500 for (int j = i + 1; j < NUM_CIRCLE; j++) { 501 circle_handle_collision_elastic(&circle[i], &circle[j]); 502 if (circle_test_collision(&circle[i], &circle[j])) 503 collision[i] = collision[j] = 1; 504 } 505 } 506 for (int i = 0; i < NUM_CIRCLE; i++) 507 if (collision[i] == 1) 508 XSetForeground(display, sgc[i], 0xFFFF00); 509 else 510 XSetForeground(display, sgc[i], 0xFF << i % 3 * 8); 511 512 // fix fps 513 // TODO: This method create some strange stripe when 514 // rendered in 60fps on a 60fps monitor. 515 dt = 0; 516 while (dt < 1.0 * 1000 * 1000 * 1000 / FPS){ 517 clock_gettime(CLOCK_MONOTONIC, &ts); 518 t1 = ts.tv_nsec; 519 dt = t1 > t0 ? t1 - t0 : t1 - t0 + 1000 * 1000 * 1000; 520 } 521 #ifdef COUNT_FPS 522 // count fps. 523 fps_count++; 524 if (t1 < t0){ 525 printf("fps: %u\n", fps_count); 526 fps_count = 0; 527 } 528 #endif 529 530 XClearArea(display, window, 531 0, 0, // position 532 win_width, win_height, // width and height 533 False); 534 for (int i = 0; i < NUM_CIRCLE; i++) { 535 XDrawArc(display, window, sgc[i], 536 circle[i].px - circle[i].r, 537 circle[i].py - circle[i].r, // position 538 circle[i].r * 2, circle[i].r * 2, 539 0, 360 << 6); // width and height 540 } 541 } 542 XSetForeground(display, gc, 0x00FFFF); 543 } 544 545 void 546 game_over(void) 547 { 548 char *menu_char = "GAME OVER"; 549 550 XClearArea(display, window, 551 0, 0, // position 552 win_width, win_height, // width and height 553 False); 554 XDrawString(display, window, gc, 555 win_width/2 - strlen(menu_char)/2, win_height/2, 556 menu_char, strlen(menu_char)); 557 XFlush(display); 558 559 sleep(1); 560 next_menu = START_MENU; 561 } 562 563 void 564 cleanup(void) 565 { 566 XCloseDisplay(display); 567 } 568 569 int 570 main(void) 571 { 572 setup(); 573 while (next_menu != QUIT){ 574 switch (next_menu){ 575 case START_MENU: 576 start_menu(); 577 break; 578 case GAME_PLAY: 579 game_play(); 580 break; 581 case GAME_OVER: 582 game_over(); 583 break; 584 default: 585 break; 586 } 587 } 588 589 cleanup(); 590 return 0; 591 }