/*  agil.c-- initialization and  the main execution loop  */
/* Copyright (C) 1996-1999,2001   Robert Masenten         */
/* This program may be redistributed under the terms of the
   GNU General Public License, version 2; see agility.h for details. */
/*                                                        */
/* This is part of the source for AGiliTy: the (Mostly)   */
/*    Universal AGT Interpreter */

/* This file contains the initialization and clean-up code
   as well as the interpreter's main loop. */

/* Overview of the source for AGiliTy:
 agil.c
  --initialization
  --tokeniser

 parser.c:
  --parser
  --disambiguation
  --menuing system

 exec.c 
  --verb execution wrapper
  --standard utilities for running actions

 runverb.c:
  --standard verbs and the main routine for executing the player's action.

 object.c
  --Utility functions for manipulating game objects (nouns, creatures, etc.)
    including scope routines.
 
 metacommand.c
  --metacommand execution: scan the metacommands and execute them
  in the proper order. The actual execution of each individual command
  is handled in token.c.

 token.c
  --this module contains the actual code to run each of the (several hundered)
  different metacommands; called from metacommand.c.


 debugcmd.c
  --Routines to execute debugging verb

 savegame.c
  --save and restore 
  --undo and restart 

 util.c
  --general utilities
  --memory management
  --buffered file i/o

 interface.c
  --high level input/output
  --general utilities

 disassemble.c
  --Metacommand debugging output

 os_<name>.c
  --low level input/output
  --other platform dependent code
*/


#include <ctype.h>
#include "agility.h"
#include "interp.h"

/*   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) {
    char *s;
    
    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=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=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<endword and the dictionary
       entry matches as far as restcnt. */
    /* endword ignores '.', though, so it could be we have a match
       but are missing it because it is period-terminated. Check this. */
    if (lookahead[restcnt]=='.')  return 0; 

    /* Otherwise, no match... try again with the next word... */
  }
  return 1; /* No matches: treat it as punctuation. */
}






static void tokenise(char *buff)
/* Convert input string into vocabulary codes */
/* 0 here denotes an unrecognized word and -1 marks the end. */
{
  int ip,j, k;
  rbool punctuation;

  j=0;ip=0;
  k=0;  /* k is the character pointer */
  for(k=0;;k++) {
    /* If PURE_DOT is set, then there are periods in some of the dictionary
       words, so '.' could be a letter or punctuation-- we have to examine
       context to figure out which. */
    if (buff[k]=='.' && PURE_DOT) 
      /* Note: check_dot is in agtdata.c, since it needs to access
	 internal implementation details of the dictionary */
      punctuation=check_dot(in_text[ip],j,buff+k+1);
    else 
      punctuation=(buff[k]==',' || buff[k]==';' || buff[k]=='.');
    if (buff[k]!=0 && !isspace(buff[k]) && !punctuation) {
	if (j<WORD_LENG-1) 
	  in_text[ip][j++]=buff[k];
    } else if (j>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,"<Sigh>  $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 (loadgame()) {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);
	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) 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=rrealloc(flag,sizeof(rbool)*(FLAG_NUM+1)); 
  for(i=1;i<=FLAG_NUM;i++) 
    flag[i]=0;
  dbgcntptr=agt_counter=rmalloc(sizeof(short)*(CNT_NUM+1));
  for(i=0;i<=CNT_NUM;i++)
    {agt_counter[i]=-1;}
  dbgvarptr=agt_var=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 <x> --> 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=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=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++)
    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;
  }
}


/* char *v */

static void print_license(void)
{
  writeln("AGiliTy");
  writestr("The (Mostly) Universal AGT Interpreter, ");
  writeln(version_str);
  writeln("  Copyright (C) 1996-1999,2001 by Robert Masenten");
  writestr("[");writestr(portstr);writeln("]");
  writeln("-----------------------------------------------------------");
  writeln("");
  writeln("  This is an interpreter for game files created with Malmberg and "
	  "Welch's _Adventure Game Toolkit_. AGiliTy is universal in the "
	  "sense that it understands and interprets most of the many versions "
	  "of the AGT game file format.");
  writeln("  It is *not* a port of the original interpreters but rather a "
	  "completely new interpreter built around the game file format; "
	  "while it follows the original interpreters on most things, there "
	  "are some differences which are described in the file "
	  "'readme.agility' which should have come with this program.");
  writeln("");
  writeln("  This software is copyright 1996-1999,2001 by Robert Masenten ");
  writeln("  This program is free software; you can redistribute it and/or "
	  "modify it under the terms of version 2 of the GNU General "
	  "Public License as published by the Free Software Foundation.");
  writeln("  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.");
  writeln("  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");
  writeln("");
  writeln("  Send comments and bug reports to Robert Masenten at:");
  writeln("      rcm-math@pacbell.net");
  writeln("");
  writeln("ACKNOWLEDGMENTS");
  writeln("Thanks to Jay Goemmer, who has sent me pages and pages of "
	  "comments and bug reports; "
	  "David Kinder, responsible for the Amiga and Windows ports and a "
	  "source of much valuble feedback; "
	  "David Youd, who has uncovered many subtle and complex bugs in "
	  "both AGiliTy and Magx; "
	  "Mitch Mlinar, who has contributed several patches and other "
	  "suggestions; "
	  "all of those who have sent me suggestions and bug reports, "
	  "including "
	  "Audrey DeLisle (responsible for the red smoke), David Doherty,"
	  "Al Golden, "
	  "John Hartnup, Walter Isaac, Sami Kivela, Alexander Lehmann, "
	  "Grant E. Metcalf, "
	  "Paul Mikell, Adam Myrow, Olav Nielsen, "
	  "D.J. Picton, Kevin Soucy, Ben Straub, \"Grand Moff Tarkin\", "
	  "Adam Thornton, "
	  "Mark Tilford, David Turpin, and Gil Williamson; "
	  "Volker Blasius,"
	  " original maintainer of the Interactive Fiction Archive; "
	  "Robert Pelak, who suggested the name \"AGiliTy\"; "
	  "and to everyone on Rec.arts.int-fiction who suggested names for "
	  "my interpreter.");
  writeln("");
  writeln("SPECIAL VERBS RECOGNIZED");
  writeln("These are all of the special verbs recognized by the interpreter:");
  writeln("  SCORE  Print out your score.");
  writeln("  NOTIFY Turn score notification on and off.");
  writeln("  INSTRUCTIONS or INS  Display the instructions for the game.");
  writeln("  INTRODUCTION or INTRO  Repeat the introduction of the game.");
  writeln("  VIEW <picture>  Views an illustration. (Not supported on all "
	  "platforms.)");
  writeln("  BRIEF   Don't print room descriptions for rooms you've seen.");
  writeln("  VERBOSE  Print room descriptions even  for rooms you've already "
	  "seen.");
  writeln("  LIST EXITS  List the exits from a room.");
  writeln("  LISTEXIT ON/OFF  Turn on/off automatic listing of exits.");
  writeln("  SCRIPT  Start sending a transcript to a file.");
  writeln("  UNSCRIPT  Stop creating a transcript.");
  writeln("  SOUND ON, OFF  Turn sound on/off.");
  writeln("  LOG  Start sending all of your commands to a file.");
  writeln("  REPLAY <number>  Replay your commands from a file, "
	  "executing one every <number> seconds.");
  writeln("  REPLAY FAST  Replay your commands from a file without waiting "
	  "for you to read the scrolling text.");
  writeln("  REPLAY STEP  Replay your commands from a file, "
	  "one for every keypress.");
  writeln("  AGILDEBUG  Access debugging commands.");
  writeln("  MENU  Toggle menu mode on or off.");
  writeln("  OOPS  Correct a word you just mistyped; must be the first "
	  "command on a line.");
  writeln("  UNDO  Undo your last move; must be the first command on a line.");
  writeln("  SAVE  Save the game.");
  writeln("  RESTORE  Restore the game.");
  writeln("  RESTART  Restart the game.");
  writeln("  QUIT  Quit.");
  writeln("");
} 


/* 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);
  do {
    if (have_ins) 
      writestr("Choose <I>nstructions, <A>GiliTy Information, "
	       "or <other> to start the game");
    else 
      writestr("Choose <A>GiliTy Information or <other> to start the game");
    choice=tolower(agt_getchar());  /* Wait for keypress */
    agt_clrscr();
    if (have_ins && choice=='i') print_instructions(fc);
    else if (choice=='a') print_license();
  } while ( (choice=='i' && have_ins) || choice=='a');
  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();exit(EXIT_SUCCESS);}
  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=rrealloc(flag,sizeof(rbool)); /* Preserve the debugging flag */

  if (DEBUG_MEM)
    printf("\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;
    mainloop();
    close_game();
  } while (doing_restore==3);
  release_file_context(&fc);
}


