/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ #include "glk/agt/agility.h" #include "glk/agt/interp.h" #include "common/str.h" namespace Glk { namespace AGT { /* ------------------------------------------------------------------- */ /* Description Pointers */ descr_ptr intro_ptr; descr_ptr title_ptr, ins_ptr; /* Only defined if agx_file is true */ descr_ptr *err_ptr; /* [NUM_ERR];*/ descr_ptr *msg_ptr; /* [MAX_MSG];*/ descr_ptr *help_ptr, *room_ptr, *special_ptr; /*[ROOM] */ descr_ptr *noun_ptr, *text_ptr, *turn_ptr, /* [NOUN] */ *push_ptr, *pull_ptr, *play_ptr; descr_ptr *talk_ptr, *ask_ptr, *creat_ptr; /* [CREAT] */ descr_ptr *quest_ptr, *ans_ptr; /* [MAX_QUEST] */ tline *question, *answer; /* [MAX_QUEST] */ /* ------------------------------------------------------------------------ */ /* Dynamically allocated data blocks (which are pointed to from elsewhere) */ char **dict; /* dict[n] points to the nth dictionary word */ long dp; /* Dictionary pointer: number of words in dict */ #define DICT_INIT 12*1024 /* Starting size of dictstr */ #define DICT_GRAN 1024 /* Granularity of dictstr size requests must be at least 4. */ char *dictstr; /* Pointer to memory block containing dict words */ long dictstrptr, dictstrsize; /* dictstrptr points to the first unused byte in dictstr. dictstrsize points to the end of the space currently allocated for dictstr. */ char *static_str; /*Static string space */ long ss_end; /* Pointer to end of used space in above */ long ss_size; /* Current size of static string space */ word *syntbl; /* Synonym list space */ slist synptr; /* Points to end of used space */ long syntbl_size; /* Long so we can catch overflows */ long descr_maxmem; char *mem_descr; /* Copy of descriptor in memory */ /* ------------------------------------------------------------------------ */ /* Data structures used internally by agtread.c */ /*The following are all set to NULL after agtread finishes. */ long *cmd_ptr; /* ME only;Points to cmd start locs in gamefile.*/ long *room_name, *noun_sdesc, *noun_pos, *creat_sdesc; long *t_pictlist, *t_pixlist, *t_songlist, *t_fontlist; /* These are only used by agtout (to allow the AGT reading routines to pass back the count of nouns inside the given object) */ integer *room_inside, *noun_inside, *creat_inside; /* This is used to translate ASCII codes */ uchar fixchar[256]; rbool text_file; /* Set if we are currently opening a binary file. */ #ifdef OPEN_AS_TEXT rbool open_as_binary; /* Open text files as binary, anyhow. */ #endif /* The following are AGT 'purity' flags; they turn off features of */ /* my interpreter that are not fully consistent with the original AGT */ /* and so could break some games. Some of these are trivial improvements; */ /* some are more radical and should be used with caution. Several are */ /* only useful if a game was designed with them in mind. */ /* In all cases, setting the flag to 1 more closely follows the */ /* behavior of the original interpreters */ /* WARNING: Most of these haven't been tested in the non-default state. */ /* Most of these will eventually become variables settable by the user */ /* or from a (possibly game-specific) configuration file */ rbool PURE_INPUT = 1; /* Is the input line bold? */ rbool PURE_TONE = 0; /* Is low level sound enabled? */ /*-------------------------------------------------------------------*/ /* Misc. things to support the tokenizer and the dictionry. */ /*-------------------------------------------------------------------*/ /* The following should not be changed without also changing the wtype enum statement in interp.h to match */ static const char *ext_voc[] = { "the", "my", "a", "an", /* These 4 are ignored in canonical AGT */ "then", ".", ";", "and", "," , "its", "all", "undo", "look", "g", "pick", "go", "exits", "talk", "take", "door", "again", "but", "except", "scene", "everything", "listexit", "listexits", "close", "verb", "noun", "adjective", "prep", "object", "name", "step", " any", "either", "both", "everyone", "everybody", "he", "she", "it", "they", "him", "her", "them", "is", "are", "oops", "was", "were", /* Everything between 'in' and 'about' should be a preposition */ "in", "out", "into", "at", "to", "across", "inside", "with", "near", "for", "of", "behind", "beside", "on", "off", "under", "from", "through", "toward", "towards", "between", "around", "upon", "thru", "by", "over", "up", "down", "about" }; /* 'about' must be the last element of this list */ /*-------------------------------------------------------------------*/ /* Routines to read in and use various auxilary files. */ /* (.TTL, .INS, .VOC, .CFG) */ /*-------------------------------------------------------------------*/ static rbool emptyline(unsigned char *s) /* Check if s consists only of white space and control characters */ { unsigned char *p; for (p = s; *p != 0; p++) if (!rspace(*p) && *p > 26) return 0; return 1; } static void print_title(fc_type fc) { int height; signed char center_mode; /* Center title? */ descr_line *buff; char *s; rbool skip_line; /* Skip first line: it has COLORS */ if (agx_file) buff = read_descr(title_ptr.start, title_ptr.size); else buff = read_ttl(fc); if (buff == NULL) { writeln(""); writeln(""); s = formal_name(fc, fNONE); if (s != NULL) { s[0] = toupper(s[0]); agt_center(1); agt_textcolor(-1); writeln(s); agt_textcolor(-2); agt_center(0); rfree(s); } writeln(""); writeln(""); if (aver < AGX00) writeln("This game was created with Malmberg and Welch's Adventure " "Game Toolkit; it is being executed by"); else writeln("This game is being executed by "); writeln(""); height = 0; } else { if (buff[0] != NULL && strncasecmp(buff[0], "COLORS", 6) == 0) { /* Do screen colors */ skip_line = 1; } else skip_line = 0; /* Compute height and count the number of non-empty lines starting with spaces. We use height as a loop variable and center_mode to store the count temporarily. */ center_mode = 0; for (height = skip_line; buff[height] != NULL; height++) if (!emptyline((uchar *)buff[height])) { if (rspace(buff[height][0])) center_mode++; else center_mode--; } if (box_title || aver == AGTCOS) center_mode = TB_CENTER; else /* includes aver==AGT135 */ if (center_mode <= 0) center_mode = TB_CENTER; else center_mode = TB_NOCENT; if (!bold_mode) agt_textcolor(-1); agt_clrscr(); textbox(buff + skip_line, height - skip_line, center_mode | (bold_mode ? 0 : TB_BOLD) | TB_TTL | (box_title ? TB_BORDER : 0)); if (!bold_mode) agt_textcolor(-2); /* Bold off */ } /* End printing of title proper */ if (agx_file) free_descr(buff); else free_ttl(buff); agt_textcolor(7); agt_center(1); if (buff != NULL) { if (aver < AGX00 && height <= screen_height - 6) writeln("[Created with Malmberg and Welch's Adventure Game Toolkit]"); if (height <= screen_height - 9) writeln(""); if (height <= screen_height - 5) writeln("This game is being executed by"); } agt_textcolor(-1); s = (char *)rmalloc(80); if (height <= screen_height - 5) sprintf(s, "AGiliTy: " "The (Mostly) Universal AGT Interpreter %s", version_str); else sprintf(s, "Being run by AGiliTy %s, " "Copyright (C) 1996-99,2001 Robert Masenten", version_str); writeln(s); rfree(s); agt_textcolor(-2); if (height <= screen_height - 5) writeln("Copyright (C) 1996-99,2001 by Robert Masenten"); if (height <= screen_height - 3) writeln(portstr); if (height <= screen_height - 10) writeln(""); agt_center(0); } /* .INS reading routines -------------------------------------- */ void print_instructions(fc_type fc) { char *buffer; uchar *s; writeln("INSTRUCTIONS:"); if (open_ins_file(fc, 1)) { /* Instruction file exists */ while (NULL != (buffer = read_ins_line())) { for (s = (uchar *)buffer; *s != 0; s++) *s = trans_ascii[*s]; writeln(buffer); } } writeln(""); } /* Routines to build the verb menu from the .VOC information */ static void build_verbmenu(void) { int i, n; char *p, *d; verbmenu = (menuentry *)rmalloc(vm_size * sizeof(menuentry)); vm_width = 0; for (i = 0; i < vm_size; i++) { p = verbmenu[i]; d = dict[verbinfo[i].verb]; n = 0; for (; n < MENU_WIDTH && *d != 0; p++, d++, n++) *p = *d; if (verbinfo[i].prep != 0 && n + 1 < MENU_WIDTH) { *p++ = ' '; d = dict[verbinfo[i].prep]; *p++ = toupper(*d++); for (; n < MENU_WIDTH && *d != 0; p++, d++, n++) *p = *d; } verbmenu[i][0] = toupper(verbmenu[i][0]); *p = 0; if (n > vm_width) vm_width = n; } } /* .CFG reading routines -------------------------------------------- */ #define opt(s) (strcasecmp(optstr[0],s)==0) /* These are the interpreter specific options; this is called from cfg_option in agtdata.c. */ void agil_option(int optnum, char *optstr[], rbool setflag, rbool lastpass) { if (opt("ibm_char")) fix_ascii_flag = !setflag; else if (!lastpass) return; /* On the first pass through the game specific file, we ignore all but the above options */ else if (opt("tone")) PURE_TONE = setflag; else if (opt("input_bold")) PURE_INPUT = setflag; else if (opt("force_load")) FORCE_VERSION = setflag; else if (!agt_option(optnum, optstr, setflag)) /* Platform specific options */ rprintf("Invalid option %s\n", optstr[0]); } /*-------------------------------------------------------------------*/ /* Tokeniser: Split input into words and look them up in dictionary */ /*-------------------------------------------------------------------*/ static rbool noise_word(word w) { if (w == ext_code[wthe] || w == ext_code[wa] || w == ext_code[wan]) return 1; if (w == ext_code[wmy]) return 1; if (aver >= AGT18 && aver <= AGT18MAX && w == ext_code[wis]) return 1; return 0; } static rbool check_dot(char *prevtext, int prevcnt, char *lookahead) /* This routine is devoted to trying to figure out whether we should treat '.' as punctuation or as a letter. */ /* It returns true if '.' should be treated as punctuation. */ /* prevtext=the current word, as far as it has been parsed. prevcnt=the number of letters in prevtext [which is *not* 0 terminated] */ /* lookahead=the rest of the current input line *after* the period. */ { int i, endword, restcnt; if (!PURE_DOT) return 1; /* No words with periods in them, so it must be punctuation. */ /* We just start scanning the dictionary to see if any of them are possible matches, looking ahead as neccessary. */ /* Find the next unambiguous word end. This ignores possible word ends caused by periods. */ for (endword = 0; lookahead[endword] != 0; endword++) if (isspace(lookahead[endword]) || lookahead[endword] == ',' || lookahead[endword] == ';') break; for (i = 0; i < dp; i++) { if (i == ext_code[wp]) continue; /* Ignore matches with the word ".". */ /* If it doesn't contain a '.' at the right location, there is no point in continuing. */ restcnt = strlen(dict[i]); if (restcnt <= prevcnt || dict[i][prevcnt] != '.') continue; /* Now make sure the previous characters are correct */ if (strncasecmp(prevtext, dict[i], prevcnt) != 0) continue; /* Finally, compare the trailing text. This is complicated by the fact that the trailing text could itself contain ambiguous '.'s */ restcnt -= prevcnt + 1; /* Number of characters in dict entry after '.' */ if (restcnt > endword) continue; /* Dictionary entry is longer than following text */ /* Check to see if the dictionary entry can be found in the lookahead buffer */ if (strncasecmp(lookahead, dict[i] + prevcnt + 1, restcnt) != 0) continue; if (restcnt == endword) return 0; /* We have a match */ /* At this point, we know that restcnt 0) { /* End of word: add it to input */ in_text[ip_][j] = 0; input[ip_] = search_dict(in_text[ip_]); if (input[ip_] == -1) input[ip_] = 0; else if (input[ip_] == 0) input[ip_] = ext_code[w_any]; /* _Real_ 'ANY' */ if (!noise_word(input[ip_])) ip_ += 1; /* i.e. if not one of the four ignored words, advance */ j = 0; } /* If j=0 and not punct, then no new word; just skip the whitespace */ if (punctuation) { in_text[ip_][0] = buff[k]; in_text[ip_][1] = 0; input[ip_] = search_dict(in_text[ip_]); if (input[ip_] == -1) input[ip_] = 0; j = 0; ip_++; } if (ip_ >= MAXINPUT - 1) { writeln("Too many words in input; ignoring rest of line."); break; } if (buff[k] == 0) break; } input[ip_] = -1; in_text[ip_][0] = 0; } /*-------------------------------------------------------------------*/ /* Main game loop: Get player input and call the parser. */ /*-------------------------------------------------------------------*/ static void game_end(void) { rbool done_flag; char *s; if (winflag || deadflag) { writeln(""); writeln(""); agt_center(1); if (winflag) gen_sysmsg(148, "***** $You$ have won! *****", MSG_MAIN, NULL); if (deadflag) gen_sysmsg(147, "***** $You$ have died! *****", MSG_MAIN, NULL); writeln(""); writeln(""); agt_center(0); } if (deadflag && !endflag) { if (curr_lives > 1) { /* Resurrection code */ if (curr_lives == max_lives) gen_sysmsg(151, "Hmmm.... so $you$'ve gotten $your$self killed. " "Would you like me to try a resurrection?", MSG_MAIN, NULL); else gen_sysmsg(152, " $You$'ve died *again*. " "Would you like me to try another resurrection?", MSG_MAIN, NULL); if (yesno("? ")) { /* Now do resurrection */ curr_lives--; quitflag = deadflag = 0; gen_sysmsg(154, "$You$ emerge coughing from a cloud of dark green smoke.", MSG_MAIN, NULL); writeln(""); loc = resurrect_room - first_room; newlife_flag = 1; set_statline(); do_look = do_autoverb = 1; newroom(); return; } else writeln("As you wish..."); } else if (max_lives > 1) gen_sysmsg(153, "$You$'ve used up all of $your$ lives.", MSG_MAIN, NULL); } writeln(""); print_score(); writeln(""); done_flag = quitflag; /* If player has QUIT, don't ask again */ while (!done_flag && !quitflag) { writestr("Would you like to "); if (restart_state != NULL) writestr("restart, "); writestr("restore"); if (undo_state != NULL && can_undo) writestr(", undo,"); else if (restart_state != NULL) writestr(","); writestr(" or quit? "); s = agt_readline(5); if (strncasecmp(s, "RESTART", 7) == 0) if (restart_state != NULL) { restart_game(); done_flag = 1; } else writeln("Sorry, I'm unable to do that because of limited memory."); else if (strncasecmp(s, "RESTORE", 7) == 0) if (g_vm->loadGame().getCode() == Common::kNoError) { done_flag = 1; } else writeln("(RESTORE failed)"); else if (strncasecmp(s, "UNDO", 4) == 0) if (can_undo && undo_state != NULL) { putstate(undo_state); done_flag = 1; } else writeln("Insufficiant memory to support UNDO"); else if (toupper(s[0]) == 'Q') { quitflag = 1; done_flag = 1; } } set_statline(); } static void parse_loop(void) /* This exists to deal with THEN lists; parse() handles the indiviudual commands */ { for (ip = 0; ip >= 0 && ip < MAXINPUT && input[ip] != -1;) { if (!parse() || quitflag || winflag || deadflag || endflag) break; if (doing_restore) break; if (ip >= 0 && ip < MAXINPUT && input[ip] != -1) writeln(""); /* Insert blank lines between commands when dealing with THEN lists */ } } static long rm_start_size; static char memstr[100]; static void mainloop(void) { char *s; doing_restore = 0; while (!quitflag) { if (DEBUG_MEM) { sprintf(memstr, "A:%ld F:%ld Delta:%ld Size:%ld+%ld=%ld (%ld left)\n", ralloc_cnt, rfree_cnt, ralloc_cnt - rfree_cnt, rm_start_size, rm_size - rm_start_size, rm_size, rm_freesize); writeln(memstr); } rm_size = 0; /* Reset it to zero */ rm_freesize = get_rm_freesize(); if (!menu_mode) { prompt_out(1); s = agt_readline(0); if (g_vm->shouldQuit()) return; agt_newline(); if (!doing_restore) tokenise(s); /* Tokenizes into input */ rfree(s); if (!doing_restore) parse_loop(); } else menu_cmd(); if (doing_restore) { if (doing_restore == 1) g_vm->loadGame(); else if (doing_restore == 2) restart_game(); else if (doing_restore == 3 || doing_restore == 4) return; /* Quit or New game requested */ doing_restore = 0; } if (winflag || deadflag || endflag || quitflag) game_end(); } } /*-------------------------------------------------------------------*/ /* Start up and shut down: Routines to initialise the game state and */ /* clean up after the game ends. */ /*-------------------------------------------------------------------*/ static int init(void) { int i, can_save; uchar *tmp1, *tmp2; init_vals(); init_creat_fix(); if (!agx_file) dict[0][0] = 0; /* Turn "ANY" into "" */ l_stat[0] = r_stat[0] = 0; /* Clear the status line */ /* lactor=lobj=lnoun=NULL;*/ tscore = old_score = objscore = 0; turncnt = 0; curr_time = startup_time; loc = start_room - first_room; cmd_saveable = 0; first_visit_flag = newlife_flag = room_firstdesc = 1; curr_lives = max_lives; /* Note: flag[0] is the debugging flag and is set elsewhere */ if (FLAG_NUM < 0) FLAG_NUM = 0; dbgflagptr = flag = (rbool *)rrealloc(flag, sizeof(rbool) * (FLAG_NUM + 1)); for (i = 1; i <= FLAG_NUM; i++) flag[i] = 0; dbgcntptr = agt_counter = (short *)rmalloc(sizeof(short) * (CNT_NUM + 1)); for (i = 0; i <= CNT_NUM; i++) { agt_counter[i] = -1; } dbgvarptr = agt_var = (long *)rmalloc(sizeof(*agt_var) * (VAR_NUM + 1)); for (i = 0; i <= VAR_NUM; i++) agt_var[i] = 0; for (i = 0; i <= maxnoun - first_noun; i++) { if (noun[i].position == NULL || noun[i].position[0] == '\0') noun[i].pos_prep = 0; else noun[i].pos_prep = -1; noun[i].pos_name = 0; noun[i].initpos = noun[i].position; } nomatch_aware = 0; /* By default, not aware. */ smart_look = 1; /* By default, LOOK --> EXAMINE */ for (i = 0; i < last_cmd; i++) { if (command[i].nouncmd == -1 || command[i].objcmd == -1 || command[i].noun_adj == -1 || command[i].obj_adj == -1 || command[i].prep == -1) nomatch_aware = 1; if (command[i].verbcmd == ext_code[wlook] && (command[i].nouncmd > 0 || command[i].noun_adj > 0 || command[i].objcmd > 0 || command[i].obj_adj > 0 || command[i].prep > 0)) smart_look = 0; } pictable = (integer *)rmalloc(sizeof(int) * maxpict); for (i = 0; i < maxpict; i++) pictable[i] = i; init_state_sys(); /* Initialize the system for saving and restoring game states */ tmp1 = (uchar *)rmalloc(MEM_MARGIN); /* Preserve some work space */ tmp2 = getstate(NULL); /* Make sure we have space to save */ if (tmp2 == NULL) can_save = 0; else can_save = 1; if (tmp2 != NULL) undo_state = getstate(NULL); else undo_state = NULL; if (undo_state != NULL) restart_state = getstate(NULL); else restart_state = NULL; rfree(tmp1); rfree(tmp2); rm_start_size = get_rm_size(); rm_freesize = get_rm_freesize(); return can_save; } static void ext_dict(void) /* Enter the vocabulary extensions into the dictionary */ { wtype i; for (i = wthe; i <= wabout; i = (wtype)((int)i + 1)) ext_code[i] = add_dict(ext_voc[i]); } static void fix_dummy(void) { int i; /* At this point, all occurances in the game file of the dictionary words have been converted to dictionary indices, and so as long as we don't change the dictionary index values, we can change the contents without interfering with the metacommand scanner (since it compares dictionary indices, not actual strings) */ if (!PURE_DUMMY) { for (i = 0; i < DUMB_VERB; i++) dict[ syntbl[auxsyn[i + BASE_VERB]] ][5] = ' '; /* Convert underscores into spaces: i.e. 'dummy_verb5' -> 'dummy verb5' */ dict[ syntbl[auxsyn[21]] ][6] = ' '; /* change_locations */ dict[ syntbl[auxsyn[55]] ][5] = ' '; /* magic_word */ } if (!PURE_SUBNAME) /* Replace the 'e' by a space */ for (i = 0; i < MAX_SUB; i++) sprintf(dict[sub_name[i]], "subroutin %d", i + 1); /* This must be no longer than 25 characters with the terminating null */ /* Now set PURE_DOT based on whether any dictionary word contains a period. */ if (aver >= AGT18 && aver <= AGT18MAX) PURE_DOT = 0; else { PURE_DOT = FORCE_PURE_DOT; for (i = 0; i < dp && !PURE_DOT; i++) if (strchr(dict[i], '.') != NULL && /* i.e. dict[i] contains period */ i != ext_code[wp]) /* The period itself _is_ a dictionary word: avoid this false match */ PURE_DOT = 1; } } /* This is a hack to get rid of the "What Now?" prompt. */ static void fix_prompt(void) { descr_line *d; if (err_ptr == NULL) return; d = read_descr(err_ptr[0].start, err_ptr[0].size); if (d == NULL) return; if (strncasecmp(d[0], "What Now?", 9) == 0) err_ptr[0].size = err_ptr[0].start = 0; free_descr(d); } void close_game(void); /* Called by setup_game, and so needs to be defined here. */ static fc_type setup_game(fc_type fc) /* game_name is the common filename of the AGT game files */ { int can_save; char choice; rbool have_ins; bold_mode = 0; rm_acct = 1; rm_trap = 1; rm_size = ralloc_cnt = rfree_cnt = 0; mars_fix = 0; no_auxsyn = 0; debug_disambig = 0; debug_any = 1; dbg_nomsg = 1; /* Supress output of MSG arguments to metacommands */ textbold = 0; debug_mode = 0; aver = 0; verboseflag = 1; notify_flag = 0; logflag = 0; menu_mode = 0; fast_replay = 0; stable_random = BATCH_MODE || make_test; if (make_test) BATCH_MODE = 0; hold_fc = fc; set_default_filenames(fc); init_stack(); read_config(agt_globalfile(0), 1); /* Global configuration file */ /* Now that we *have* PATH information, go looking for the games */ /* At the very least, it creates an rmalloc'd copy of game_name */ read_config(openfile(fc, fCFG, NULL, 0), 0); text_file = 0; /* First pass through game specific config file */ build_trans_ascii(); #ifdef PROFILE resetwatch(); #endif writeln("Loading game..."); if (!read_agx(fc, 0) && !readagt(fc, 0)) fatal("Unable to load game."); #ifdef PROFILE writeln(stopwatch()); agt_waitkey(); #endif if (have_opt) menu_mode = opt_data[5]; /* See agtread.c for discussion of OPT file format */ text_file = 1; read_config(openfile(fc, fCFG, NULL, 0), 1); /*Game specific config file*/ text_file = 0; if (min_ver > AGIL_VERID) { if (FORCE_VERSION) agtwarn("This game requires a later version of AGiliTy.", 0); else fatal("This game requires a later version of AGiliTy."); } sort_cmd(); ext_dict(); build_verbmenu(); fix_dummy(); /* Prevent player from calling dummy verbs or subroutines by typing 'Subroutine n' on the command line */ can_save = init(); if (!agx_file) open_descr(fc); fix_prompt(); /* Kill off 'What Now?' prompt. */ if (BATCH_MODE || make_test) set_test_mode(fc); start_interface(fc); fontcmd(2, 0); /* Set initial font */ if (intro_first && intro_ptr.size > 0) { agt_clrscr(); print_descr(intro_ptr, 1); wait_return(); } if (aver >= AGTME10) pictcmd(3, 0); /* Show title image, if there is one */ print_title(fc); have_ins = open_ins_file(fc, 0); if (have_ins) { do { writestr("Choose nstructions, or to start the game"); choice = tolower(agt_getchar()); /* Wait for keypress */ if (g_vm->shouldQuit()) return nullptr; agt_clrscr(); if (have_ins && choice == 'i') print_instructions(fc); } while (choice == 'i'); } close_ins_file(); if (!intro_first && intro_ptr.size > 0) { print_descr(intro_ptr, 1); wait_return(); agt_clrscr(); } if (maxroom < first_room) { close_game(); error("Invalid first room"); } set_statline(); if (can_save == 0) { writeln("[Insufficiant memory to support SAVE, RESTORE, or UNDO]"); } else if (undo_state == NULL) writeln("[Insufficiant memory to support UNDO]"); do_look = do_autoverb = 1; newroom(); rm_acct = 1; /* Turn on memory allocation accounting */ return fc; } /* We need to import save_lnoun from exec.c so that we can free it. */ extern parse_rec *save_lnoun; void close_game(void) { if (agx_file) agx_close_descr(); else close_descr(); fontcmd(1, -1); /* Restore original font */ musiccmd(7, -1); /* Clean up */ close_interface(); /* Now free everything in sight; this _shouldn't_ be necessary, but why take chances? */ free_all_agtread(); rfree(restart_state); rfree(undo_state); rfree(pictable); rfree(save_lnoun); rfree(verbptr); rfree(verbend); rfree(agt_counter); rfree(agt_var); free_creat_fix(); flag = (rbool *)rrealloc(flag, sizeof(rbool)); /* Preserve the debugging flag */ if (DEBUG_MEM) debug("\n\nAlloc:%ld Freed:%ld Difference:%ld\n", ralloc_cnt, rfree_cnt, ralloc_cnt - rfree_cnt); } void run_game(fc_type fc) { doing_restore = 0; rm_acct = 1; rm_trap = 1; rm_size = ralloc_cnt = rfree_cnt = 0; read_config(agt_globalfile(0), 1); /* Global configuration file: get PATH information*/ fix_file_context(fc, fDA1); do { if (doing_restore == 3) { release_file_context(&fc); fc = setup_game(new_game()); } else setup_game(fc); doing_restore = 0; if (!g_vm->shouldQuit()) mainloop(); close_game(); if (g_vm->shouldQuit()) break; } while (doing_restore == 3); release_file_context(&fc); } } // End of namespace AGT } // End of namespace Glk