main.c (12571B)
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <stdint.h> 4 #include <time.h> 5 #include <unistd.h> 6 #include <string.h> 7 8 #include <sys/resource.h> // for debuging of memory leak 9 10 #include "list.h" 11 #include "object.h" 12 #include "draw.h" 13 #include "world_map.h" 14 15 /* 16 #define COUNT_FPS 17 */ 18 19 /* constants */ 20 enum { 21 FPS = 60, 22 SUB_FRAME = 4, 23 GRAVITY = 1000, 24 WIN_WIDTH = 800, 25 WIN_HEIGHT = 600, 26 }; 27 28 enum keys { 29 KEY_Q, 30 KEY_I, 31 KEY_D, 32 KEY_S, 33 KEY_A, 34 KEY_W, 35 KEY_SPACE, 36 NUM_KEY, //number of keys in this enum 37 }; 38 enum key_state { 39 KEY_UP, 40 KEY_DOWN, 41 }; 42 enum next_menu { 43 START_MENU, 44 GAME_PLAY, 45 GAME_OVER, 46 GAME_CLEAR, 47 QUIT, 48 }; 49 enum debug_msg { 50 DFPS, 51 DONFLOOR, 52 DPOS, 53 DVEL, 54 DMEM, 55 NDMSG, 56 DMAXLEN = 128, 57 }; 58 59 /* variables */ 60 enum next_menu next_menu = START_MENU; 61 void (* col_func[NUM_OBJ_TYPE][NUM_OBJ_TYPE])(Object *, Object *) = {0}; 62 63 /* function prototypes */ 64 /* menus */ 65 void start_menu(void); 66 void game_play(void); 67 void game_over(void); 68 void game_clear(void); 69 /* events */ 70 void receive_events(enum key_state[]); 71 void handle_inputs(enum key_state[], Object *, int *); 72 /* collision functions */ 73 void col_pf(Object *, Object *); // player-flag 74 void col_pb(Object *, Object *); // player-block 75 void col_pe(Object *, Object *); // player-enemy 76 void col_eb(Object *, Object *); // enemy-block 77 void col_ee(Object *, Object *); // enemy-enemy 78 79 /* misc: these functions use interfaces from multiple header files */ 80 /* TODO: I want to rewrite these more cleanly */ 81 void draw_object(const Object *); 82 void draw_object_scroll(const Object *, int); 83 84 void 85 start_menu(void) 86 { 87 char *menu_char_q = "press q to quit."; 88 char *menu_char_s = "press <space> to start."; 89 90 x_clear_area(); 91 x_draw_string(0x00FFFF, 92 WIN_WIDTH / 2 - 10 * strlen(menu_char_q)/2, 93 WIN_HEIGHT / 2, 94 menu_char_q, strlen(menu_char_q)); 95 x_draw_string(0x00FFFF, 96 WIN_WIDTH / 2 - 10 * strlen(menu_char_s)/2, 97 WIN_HEIGHT / 2 + 20, 98 menu_char_s, strlen(menu_char_s)); 99 100 while (next_menu == START_MENU) { 101 char c; 102 int event = x_next_event(&c); 103 104 switch (event) { 105 case XEXPOSE: 106 x_draw_string(0x00FFFF, 107 WIN_WIDTH / 2 - 10 * strlen(menu_char_q)/2, 108 WIN_HEIGHT / 2, 109 menu_char_q, strlen(menu_char_q)); 110 x_draw_string(0x00FFFF, 111 WIN_WIDTH / 2 - 10 * strlen(menu_char_s)/2, 112 WIN_HEIGHT / 2 + 20, 113 menu_char_s, strlen(menu_char_s)); 114 115 break; 116 case XWINDEL: 117 next_menu = QUIT; 118 break; 119 case XKEYPRESS: 120 switch (c) { 121 case 'q': 122 next_menu = QUIT; 123 break; 124 case ' ': 125 next_menu = GAME_PLAY; 126 break; 127 default: 128 break; 129 } 130 break; 131 default: 132 break; 133 } 134 } 135 } 136 137 void 138 game_play(void) 139 { 140 enum key_state key_state[NUM_KEY]; 141 long t0, t1, dt; 142 struct timespec ts; 143 List *ol = NULL; // block list 144 Object *player = NULL; 145 List *olc = NULL; // cursor used to scan ol 146 int scroll_dst = 0; 147 int scroll_margin = WIN_WIDTH * 2 / 5; 148 149 int debug = 0; 150 int frame_count = 0; 151 int cur_fps = 0; 152 char debug_msg[NDMSG][DMAXLEN] = {0}; 153 154 struct rusage rusage; // for debuging of memory leak 155 156 /* setup collision handler */ 157 col_func[TPLAYER][TFLAG] = col_func[TFLAG][TPLAYER] = &col_pf; 158 col_func[TPLAYER][TBLOCK] = col_func[TBLOCK][TPLAYER] = &col_pb; 159 col_func[TPLAYER][TENEMY] = col_func[TENEMY][TPLAYER] = &col_pe; 160 col_func[TENEMY][TBLOCK] = col_func[TBLOCK][TENEMY] = &col_eb; 161 col_func[TENEMY][TENEMY] = col_func[TENEMY][TENEMY] = &col_ee; 162 163 /* load world into ol */ 164 for (int i = 0; i < WORLD_WIDTH * WORLD_HEIGHT; i++) { 165 if (world_map[i] == 'b') { 166 olc = laddfront(ol, create_object(TBLOCK, 0x00FF00, 167 i % WORLD_WIDTH * BLOCK_SIZE, 168 i / WORLD_WIDTH * BLOCK_SIZE, 169 0, 0, 0, 0, 170 BLOCK_SIZE, BLOCK_SIZE, 171 BLOCK_SIZE * BLOCK_SIZE, 172 NULL)); 173 if (olc == NULL) { 174 fprintf(stderr, "main: error. adding item to list ol\n"); 175 } else { 176 ol = olc; 177 } 178 } else if (world_map[i] == 'p') { 179 player = create_object(TPLAYER, 0x0000FF, 180 i % WORLD_WIDTH * BLOCK_SIZE, 181 i / WORLD_WIDTH * BLOCK_SIZE, 182 0, 0, 0, GRAVITY, 183 BLOCK_SIZE, BLOCK_SIZE, 184 BLOCK_SIZE * BLOCK_SIZE, 185 NULL); 186 olc = laddfront(ol, player); 187 if (player == NULL || olc == NULL) { 188 fprintf(stderr, "main: error. creating player\n"); 189 exit(1); 190 } else { 191 ol = olc; 192 } 193 } else if (world_map[i] == 'e') { 194 olc = laddfront(ol, create_object(TENEMY, 0xFF0000, 195 i % WORLD_WIDTH * BLOCK_SIZE, 196 i / WORLD_WIDTH * BLOCK_SIZE, 197 -10, 0, 0, GRAVITY, 198 BLOCK_SIZE, BLOCK_SIZE, 199 BLOCK_SIZE * BLOCK_SIZE, 200 NULL)); 201 if (olc == NULL) { 202 fprintf(stderr, "main: error. adding item to list ol\n"); 203 } else { 204 ol = olc; 205 } 206 } else if (world_map[i] == 'f') { 207 olc = laddfront(ol, create_object(TFLAG, 0xFFFF00, 208 i % WORLD_WIDTH * BLOCK_SIZE, 209 i / WORLD_WIDTH * BLOCK_SIZE, 210 0, 0, 0, 0, 211 BLOCK_SIZE, BLOCK_SIZE, 212 BLOCK_SIZE * BLOCK_SIZE, 213 NULL)); 214 if (olc == NULL) { 215 fprintf(stderr, "main: error. adding item to list ol\n"); 216 } else { 217 ol = olc; 218 } 219 } 220 } 221 222 while (next_menu == GAME_PLAY){ 223 receive_events(key_state); 224 handle_inputs(key_state, player, &debug); 225 if (debug) { 226 snprintf(debug_msg[DONFLOOR], DMAXLEN, 227 "on_floor: %d", player->on_floor); 228 } 229 230 clock_gettime(CLOCK_MONOTONIC, &ts); 231 t0 = ts.tv_nsec; 232 233 for (int k = 0; k < SUB_FRAME; k++) { 234 for (olc = ol; olc != NULL; olc = olc->next) { 235 ((Object *)olc->item)->next_tick((Object *)olc->item, 236 1e9 / FPS / SUB_FRAME); 237 } 238 239 // game over when fall out of the world 240 if (player->p.y > WORLD_HEIGHT * BLOCK_SIZE) { 241 next_menu = GAME_OVER; 242 break; 243 } 244 // bind within the world 245 if (player->p.x < 0) { 246 player->p.x = 0; 247 if (player->v.x < 0) 248 player->v.x = 0; 249 } 250 if (WORLD_WIDTH * BLOCK_SIZE < player->p.x + 251 player->body.rectangle.w) { 252 player->p.x = WORLD_WIDTH * BLOCK_SIZE - 253 player->body.rectangle.w; 254 if (player->v.x > 0) 255 player->v.x = 0; 256 } 257 258 // scrolling 259 if (player->p.x - scroll_dst > WIN_WIDTH - scroll_margin) { 260 scroll_dst = player->p.x - WIN_WIDTH + scroll_margin; 261 if (scroll_dst > WORLD_WIDTH * BLOCK_SIZE - WIN_WIDTH) 262 scroll_dst = WORLD_WIDTH * BLOCK_SIZE - WIN_WIDTH; 263 } 264 if (player->p.x - scroll_dst < scroll_margin) { 265 scroll_dst = player->p.x - scroll_margin; 266 if (scroll_dst < 0) 267 scroll_dst = 0; 268 } 269 270 player->on_floor = 0; 271 if (debug) { 272 snprintf(debug_msg[DVEL], DMAXLEN, 273 "vel: (%3.0f, %3.0f)", player->v.x, player->v.y); 274 } 275 276 277 lhandle_collision(ol); 278 } 279 280 // fix fps 281 // TODO: This method create some strange stripe when 282 // rendered in 60fps on a 60fps monitor. 283 dt = 0; 284 while (dt < 1.0 * 1e9 / FPS){ 285 clock_gettime(CLOCK_MONOTONIC, &ts); 286 t1 = ts.tv_nsec; 287 dt = t1 > t0 ? t1 - t0 : t1 - t0 + 1e9; 288 } 289 290 // count fps. 291 if (debug) { 292 frame_count++; 293 if (t1 < t0){ 294 cur_fps = frame_count; 295 snprintf(debug_msg[DFPS], DMAXLEN, "fps: %d", cur_fps); 296 frame_count = 0; 297 } 298 299 snprintf(debug_msg[DPOS], DMAXLEN, 300 "pos: (%3.2f, %3.2f)", player->p.x, player->p.y); 301 302 getrusage(RUSAGE_SELF, &rusage); 303 snprintf(debug_msg[DMEM], DMAXLEN, "mem: %ld", rusage.ru_maxrss); 304 } 305 306 x_clear_area(); 307 for (olc = ol; olc != NULL; olc = olc->next) { 308 draw_object_scroll((Object *)olc->item, scroll_dst); 309 } 310 311 if (debug) { 312 for (int i = 0; i < NDMSG; i++) { 313 x_draw_string(0xFFFFFF, 314 10, (i + 1) * 12, 315 debug_msg[i], strlen(debug_msg[i])); 316 } 317 } 318 } 319 lfreei(ol, (void (*)(void *))&free_object); 320 } 321 322 void 323 game_over(void) 324 { 325 char *menu_char = "GAME OVER"; 326 327 x_draw_string(0x00FFFF, 328 WIN_WIDTH / 2 - 10 * strlen(menu_char)/2, 329 WIN_HEIGHT / 2, 330 menu_char, strlen(menu_char)); 331 x_flush(); 332 333 sleep(1); 334 while(x_pending() > 0) { 335 x_next_event(NULL); 336 } 337 next_menu = START_MENU; 338 } 339 340 void 341 game_clear(void) 342 { 343 char *menu_char = "GAME CLEAR"; 344 345 x_draw_string(0x00FFFF, 346 WIN_WIDTH / 2 - 10 * strlen(menu_char)/2, 347 WIN_HEIGHT / 2, 348 menu_char, strlen(menu_char)); 349 x_flush(); 350 351 sleep(1); 352 while(x_pending() > 0) { 353 x_next_event(NULL); 354 } 355 next_menu = START_MENU; 356 } 357 358 void 359 receive_events(enum key_state key_state[]) 360 { 361 while (x_pending() > 0) { 362 char c; 363 int event = x_next_event(&c); 364 365 switch (event) { 366 case XWINDEL: 367 next_menu = QUIT; 368 break; 369 case XKEYPRESS: 370 switch (c) { 371 case 'q': 372 key_state[KEY_Q] = KEY_DOWN; 373 break; 374 case 'i': 375 key_state[KEY_I] = KEY_DOWN; 376 break; 377 case 'd': 378 key_state[KEY_D] = KEY_DOWN; 379 break; 380 case 'a': 381 key_state[KEY_A] = KEY_DOWN; 382 break; 383 case 'w': 384 key_state[KEY_W] = KEY_DOWN; 385 break; 386 case 's': 387 key_state[KEY_S] = KEY_DOWN; 388 break; 389 case ' ': 390 key_state[KEY_SPACE] = KEY_DOWN; 391 break; 392 default: 393 break; 394 } 395 break; 396 case XKEYRELEASE: 397 switch (c) { 398 case 'q': 399 key_state[KEY_Q] = KEY_UP; 400 break; 401 case 'i': 402 key_state[KEY_I] = KEY_UP; 403 break; 404 case 'd': 405 key_state[KEY_D] = KEY_UP; 406 break; 407 case 'a': 408 key_state[KEY_A] = KEY_UP; 409 break; 410 case 'w': 411 key_state[KEY_W] = KEY_UP; 412 break; 413 case 's': 414 key_state[KEY_S] = KEY_UP; 415 break; 416 case ' ': 417 key_state[KEY_SPACE] = KEY_UP; 418 break; 419 default: 420 break; 421 } 422 break; 423 } 424 } 425 } 426 427 void 428 handle_inputs(enum key_state key_state[], Object *player, int *debug) 429 { 430 static int key_i_before = KEY_UP; 431 432 if (key_state[KEY_Q] == KEY_DOWN) { 433 next_menu = GAME_OVER; 434 return; 435 } 436 if (key_state[KEY_I] == KEY_DOWN && key_i_before == KEY_UP) { 437 *debug = !*debug; 438 key_i_before = KEY_DOWN; 439 } 440 if (key_state[KEY_I] == KEY_UP && key_i_before == KEY_DOWN) { 441 key_i_before = KEY_UP; 442 } 443 444 if (key_state[KEY_D] == KEY_DOWN && key_state[KEY_A] != KEY_DOWN) { 445 if (player->v.x > 0) { 446 player->a.x = 500; 447 } else { 448 player->a.x = 1000; 449 } 450 } else if (key_state[KEY_A] == KEY_DOWN && key_state[KEY_D] != KEY_DOWN) { 451 if (player->v.x > 0) { 452 player->a.x = -1000; 453 } else { 454 player->a.x = -500; 455 } 456 } else { 457 if (player->on_floor) 458 player->a.x = -3 * player->v.x; 459 else 460 player->a.x = -player->v.x; 461 } 462 463 if (player->v.x < -200) player->v.x = -200; 464 if (player->v.x > 200) player->v.x = 200; 465 if (player->on_floor && key_state[KEY_SPACE] == KEY_DOWN) { 466 player->v.y = -450; 467 } 468 } 469 470 void 471 col_pf(Object *o0, Object *o1) 472 { 473 if (!test_collision(o0, o1)) 474 return; 475 if (o0->type == TFLAG && o1->type == TPLAYER) 476 handle_collision_mf(o1, o0); 477 else if (o0->type == TPLAYER && o1->type == TFLAG) 478 handle_collision_mf(o0, o1); 479 else 480 fprintf(stderr, "col_pf: Error. invalid object types\n"); 481 next_menu = GAME_CLEAR; 482 } 483 484 void 485 col_pb(Object *op, Object *ob) 486 { 487 if (op->type == TBLOCK && ob->type == TPLAYER) { 488 handle_collision_mf(ob, op); 489 if(is_on_floor(ob, op)) 490 ob->on_floor = 1; 491 } else if (op->type == TPLAYER && ob->type == TBLOCK) { 492 handle_collision_mf(op, ob); 493 if(is_on_floor(op, ob)) 494 op->on_floor = 1; 495 } else { 496 fprintf(stderr, "col_pb: invalid object type: [%d, %d]\n", 497 op->type, ob->type); 498 } 499 } 500 501 void 502 col_eb(Object *oe, Object *ob) 503 { 504 float pvx; 505 if (oe->type == TBLOCK && ob->type == TENEMY) { 506 pvx = ob->v.x; 507 handle_collision_mf(ob, oe); 508 if (ob->v.x == 0) 509 ob->v.x = -pvx; 510 } else if (oe->type == TENEMY && ob->type == TBLOCK) { 511 pvx = oe->v.x; 512 handle_collision_mf(oe, ob); 513 if (oe->v.x == 0) 514 oe->v.x = -pvx; 515 } else { 516 fprintf(stderr, "col_pb: invalid object type: [%d, %d]\n", 517 oe->type, ob->type); 518 } 519 } 520 521 void 522 col_ee(Object *o0, Object *o1) 523 { 524 o0->v.x *= -1; 525 o1->v.x *= -1; 526 } 527 528 void 529 col_pe(Object *op, Object *oe) 530 { 531 next_menu = GAME_OVER; 532 } 533 534 535 void 536 draw_object(const Object *o) 537 { 538 if (o->shape == SRECTANGLE) { 539 x_draw_rectangle(o->color, 540 o->p.x, o->p.y, 541 o->body.rectangle.w, 542 o->body.rectangle.h); 543 } else { 544 fprintf(stderr, "draw_object: Drawing object other than rectangle is" 545 "not implemented yet.\n"); 546 } 547 } 548 549 void 550 draw_object_scroll(const Object *o, int sd) 551 { 552 if (o->shape == SRECTANGLE) { 553 if (sd - o->body.rectangle.w <= o->p.x && 554 o->p.x < WIN_WIDTH + sd) { 555 x_draw_rectangle(o->color, 556 o->p.x - sd, o->p.y, 557 o->body.rectangle.w, 558 o->body.rectangle.h); 559 } 560 } else { 561 fprintf(stderr, "draw_object_scroll: Drawing object other than" 562 "rectangle is not implemented yet.\n"); 563 } 564 } 565 566 int 567 main(void) 568 { 569 x_setup_window(0, 0, 570 WIN_WIDTH, WIN_HEIGHT, 571 0x000000, "UNKO"); 572 while (next_menu != QUIT){ 573 switch (next_menu){ 574 case START_MENU: 575 start_menu(); 576 break; 577 case GAME_PLAY: 578 game_play(); 579 break; 580 case GAME_OVER: 581 game_over(); 582 break; 583 case GAME_CLEAR: 584 game_clear(); 585 break; 586 default: 587 break; 588 } 589 } 590 591 x_clean_up(); 592 return 0; 593 }