/* * game_loop * DESCRIPTION: Main event loop for the adventure game. * INPUTS: none * OUTPUTS: none * RETURN VALUE: GAME_QUIT if the player quits, or GAME_WON if they have won * SIDE EFFECTS: drives the display, etc. */ static game_condition_t game_loop () { /* * Variables used to carry information between event loop ticks; see * initialization below for explanations of purpose. */ struct timeval start_time, tick_time; struct timeval cur_time; /* current time (during tick) */ cmd_t cmd; /* command issued by input control */ int32_t enter_room; /* player has changed rooms */ /* Record the starting time--assume success. */ (void)gettimeofday (&start_time, NULL); /* Calculate the time at which the first event loop tick should occur. */ tick_time = start_time; if ((tick_time.tv_usec += TICK_USEC) > 1000000) { tick_time.tv_sec++; tick_time.tv_usec -= 1000000; } /* The player has just entered the first room. */ enter_room = 1; /* The main event loop. */ while (1) { /* * Update the screen, preparing the VGA palette and photo-drawing * routines and drawing a new room photo first if the player has * entered a new room, then showing the screen (and status bar, * once you have it working). */ if (enter_room) { /* Reset the view window to (0,0). */ game_info.map_x = game_info.map_y = 0; set_view_window (game_info.map_x, game_info.map_y); /* Discard any partially-typed command. */ reset_typed_command (); /* Adjust colors and photo drawing for the current room photo. */ prep_room (game_info.where); /* Draw the room (calls show. */ redraw_room (); /* Only draw once on entry. */ enter_room = 0; } show_screen (); //lock status_msg to prevent changes (void)pthread_mutex_lock (&msg_lock); room_t* curr_room = game_info.where; // This is the current room pointer // Now call fill_status_bar with all the possible strings as params fill_status_bar(room_name(curr_room), get_typed_command(), status_msg); (void)pthread_mutex_unlock (&msg_lock); //unlock // Calculate game time display_time_on_tux (cur_time.tv_sec - start_time.tv_sec); /* * Wait for tick. The tick defines the basic timing of our * event loop, and is the minimum amount of time between events. */ do { if (gettimeofday (&cur_time, NULL) != 0) { /* Panic! (should never happen) */ clear_mode_X (); shutdown_input (); perror ("gettimeofday"); exit (3); } } while (!time_is_after (&cur_time, &tick_time)); /* * Advance the tick time. If we missed one or more ticks completely, * i.e., if the current time is already after the time for the next * tick, just skip the extra ticks and advance the clock to the one * that we haven't missed. */ do { if ((tick_time.tv_usec += TICK_USEC) > 1000000) { tick_time.tv_sec++; tick_time.tv_usec -= 1000000; } } while (time_is_after (&cur_time, &tick_time)); /* * Handle asynchronous events. These events use real time rather * than tick counts for timing, although the real time is rounded * off to the nearest tick by definition. */ /* (none right now...) */ /* * Handle synchronous events--in this case, only player commands. * Note that typed commands that move objects may cause the room * to be redrawn. */ cmd = get_command (); switch (cmd) { case CMD_UP: move_photo_down (); break; case CMD_RIGHT: move_photo_left (); break; case CMD_DOWN: move_photo_up (); break; case CMD_LEFT: move_photo_right (); break; case CMD_MOVE_LEFT: enter_room = (TC_CHANGE_ROOM == try_to_move_left (&game_info.where)); break; case CMD_ENTER: enter_room = (TC_CHANGE_ROOM == try_to_enter (&game_info.where)); break; case CMD_MOVE_RIGHT: enter_room = (TC_CHANGE_ROOM == try_to_move_right (&game_info.where)); break; case CMD_TYPED: if (handle_typing ()) { enter_room = 1; } break; case CMD_QUIT: return GAME_QUIT; default: break; } // Repeat the same thing for the tux cmd = get_tux_command(); switch (cmd) { case CMD_UP: move_photo_down (); break; case CMD_RIGHT: move_photo_left (); break; case CMD_DOWN: move_photo_up (); break; case CMD_LEFT: move_photo_right (); break; case CMD_MOVE_LEFT: enter_room = (TC_CHANGE_ROOM == try_to_move_left (&game_info.where)); break; case CMD_ENTER: enter_room = (TC_CHANGE_ROOM == try_to_enter (&game_info.where)); break; case CMD_MOVE_RIGHT: enter_room = (TC_CHANGE_ROOM == try_to_move_right (&game_info.where)); break; case CMD_QUIT: return GAME_QUIT; default: break; } /* If player wins the game, their room becomes NULL. */ if (NULL == game_info.where) { return GAME_WON; } } /* end of the main event loop */ }
/* * handle_typing * DESCRIPTION: Parse and execute a typed command. * INPUTS: none (reads typed command) * OUTPUTS: none * RETURN VALUE: 1 if the player's room changes, 0 otherwise * SIDE EFFECTS: may move the player, move objects, and/or redraw the screen */ static int32_t handle_typing () { const char* cmd; /* command verb typed */ int32_t cmd_len; /* length of command verb */ const char* arg; /* argument given to command verb */ int32_t idx; /* loop index over command list */ tc_action_t result; /* result of typed command execution */ /* Read the command and strip leading spaces. If it's empty, return. */ cmd = get_typed_command (); while (' ' == *cmd) { cmd++; } if ('\0' == *cmd) { return 0; } /* * Walk over the command verb, calculating its length as we go. Space * or NUL marks the end of the verb, after which the argument begins. * Leading spaces are first stripped from the argument, but we make no * attempt to deal with trailing spaces (argument names must match * exactly). */ for (cmd_len = 0; ' ' != cmd[cmd_len] && '\0' != cmd[cmd_len]; cmd_len++); arg = &cmd[cmd_len]; while (' ' == *arg) { arg++; } /* Compare the typed verb with each command in our list. */ for (idx = 0; NULL != cmd_list[idx].name; idx++) { /* If the typed verb is not long enough, it can't match. */ if (cmd_list[idx].min_len > cmd_len) { continue; } /* Compare the prefix of the command with the typed verb. */ if (0 != strncasecmp (cmd_list[idx].name, cmd, cmd_len)) { continue; } /* Execute the command found. */ switch (cmd_list[idx].cmd) { case TC_BUY: result = typed_cmd_buy (&game_info.where, arg); break; case TC_CHARGE: result = typed_cmd_charge (&game_info.where, arg); break; case TC_DO: result = typed_cmd_do (&game_info.where, arg); break; case TC_DRINK: result = typed_cmd_drink (&game_info.where, arg); break; case TC_DROP: result = typed_cmd_drop (&game_info.where, arg); if (!player_has_board ()) { game_info.x_speed = MOTION_SPEED; } if (!player_has_jetpack ()) { game_info.y_speed = MOTION_SPEED; } break; case TC_FIX: result = typed_cmd_fix (&game_info.where, arg); break; case TC_FLASH: result = typed_cmd_flash (&game_info.where, arg); break; case TC_GET: result = typed_cmd_get (&game_info.where, arg); if (player_has_board ()) { game_info.x_speed = MOTION_SPEED * 3; } if (player_has_jetpack ()) { game_info.y_speed = MOTION_SPEED * 3; } break; case TC_GO: result = typed_cmd_go (&game_info.where, arg); break; case TC_INSTALL: result = typed_cmd_install (&game_info.where, arg); break; case TC_INVENTORY: result = typed_cmd_inventory (&game_info.where, arg); break; case TC_SIGH: result = typed_cmd_sigh (&game_info.where, arg); break; case TC_USE: result = typed_cmd_use (&game_info.where, arg); break; case TC_WEAR: result = typed_cmd_wear (&game_info.where, arg); break; default: show_status ("Bug...!"); result = TC_ALLOW_EDIT; break; } /* Handle command result and return. */ if (TC_CHANGE_ROOM == result) { return 1; } if (TC_ALLOW_EDIT != result) { reset_typed_command (); if (TC_REDRAW_ROOM == result) { redraw_room (); } } return 0; } /* The command was not recognized. */ show_status ("What are you babbling about?"); return 0; }
/* * status_thread * DESCRIPTION: Function executed by status message helper thread. * Waits for a message to be displayed, then shows the * message for 1.5 seconds before deleting it. If a * new message has appeared in the meantime, restarts the * clock and tries again. * INPUTS: none (ignored) * OUTPUTS: none * RETURN VALUE: NULL * SIDE EFFECTS: Changes the status message to an empty string. */ static void* status_thread (void* ignore) { struct timespec ts; /* absolute wake-up time */ while (1) { /* * Wait for a message to appear. Note that we must check the * condition after acquiring the lock, and that pthread_cond_wait * yields the lock, then reacquires the lock before returning. */ (void)pthread_mutex_lock (&msg_lock); while ('\0' == status_msg[0]) { print_status_text(room_name(game_info.where), STATUS_ROOM_COLOR, STATUS_BG_COLOR, ALIGN_LEFT, 1); print_status_text(get_typed_command(), STATUS_COMMAND_COLOR, STATUS_BG_COLOR, ALIGN_RIGHT, 0); pthread_cond_wait (&msg_cv, &msg_lock); } /* * A message is present: if we stop before the timeout * passes, assume that a new one has been posted; if the * timeout passes, clear the message and wait for a new one. */ do { /* Get the current time. */ clock_gettime (CLOCK_REALTIME, &ts); const char *command = get_typed_command(); int alignment = ALIGN_CENTER; if (command[0] != '\0') { alignment = ALIGN_LEFT; } print_status_text(status_msg, STATUS_FG_COLOR, STATUS_BG_COLOR, alignment, 1); print_status_text(command, STATUS_COMMAND_COLOR, STATUS_BG_COLOR, ALIGN_RIGHT, 0); /* Add 1.5 seconds to it. */ if (500000000 <= ts.tv_nsec) { ts.tv_sec += 2; ts.tv_nsec -= 500000000; } else { ts.tv_sec += 1; ts.tv_nsec += 500000000; } /* * And go to sleep. If we wake up due to anything but a * timeout, we assume (possibly incorrectly) that a new * message has appeared and try to wait 1.5 seconds again. */ } while (ETIMEDOUT != pthread_cond_timedwait (&msg_cv, &msg_lock, &ts)); /* * Clear the message, then release the lock (remember that * pthread_cond_timedwait reacquires the lock before returning). */ status_msg[0] = '\0'; (void)pthread_mutex_unlock (&msg_lock); } /* This code never executes--the thread should always be cancelled. */ return NULL; }