/* * qa.c v 1.0 james bumgardner 6/86 * voice: 818-766-0594 * (Microsoft C) * * A simple expert system interpreter * * Description * * This program constructs a binary tree using the data contained in a text * file. It then queries the user for directions for traversing the tree. * This technique can be used for designing simple expert systems, or games. * * The best way to see how the trees work is to examine the tree files that * are included in this package. Each node starts with a comment (*) and * ends with a symbol indicating which type of node it is. Generally, each * node contains either a multiple choice question with single letter * responses, or a solution. Each response points to a child node in the * tree which contains either a new question or solution. Child nodes are * indented. * * Multiple Choice (>) * * A multiple choice node contains lines of text (usually a screenful or * less) followed by the directive ">" and the appropriate answers * (e.g. ">YN"). * The number of answers is unlimited, but it is best to use fewer than 16. * * Solution (!) * * A solution is a node without any children. Upon reaching a solution, QA * diplays the text and says "Hit any key to continue". The user is then put * back on the root node. * * Comment (*) * * Generally, each node area in the text file starts with an asterisk character * followed by the title for the node. This is the single letter response used * to reach the node. (The root node has a dummy title of Y). These comments * are not required, but are useful for debugging. You can use the debug * option to make sure that the tree loads in correctly. For each node * loaded, it shows the expected title followed by the comment. * * Backtracking (^) * * When constructing trees you will find that some answers or sub-trees recurr * often. You can have more than one node backtrack into the same area using * a backtrack node. When hit, the qa routine will backrack once (up to the * parent), and then follow the backtrack directions as if they were entered at * the keyboard. For example, the directive "^//YN" will backtrack thrice, and * then answer Yes, No. * * Random forking (~) * * You can design games by using the Random fork node to create random responses * to questions. A random fork node is like a multiple choice node, except * no question is asked, instead, one of the answers is automatically * chosen. Currently, each of the children of a random fork has an equal * chance of getting chosen, but you can modify the source code to allow * for greater control. You are limited to 16 children for each random node. * * Chaining ($) * * You can keep individual sub-trees in different files. Use the chaining * directive to have QA combine files into one tree. * */ #include "stdio.h" #include "ctype.h" #define BS 8 #define RET 13 #define CTRLZ 26 #define ESC 27 #define HOME 327 #define UP 328 #define LEFT 331 #define END 335 #define F1 315 #define NODE struct node struct node { char nam,typ,*txt; struct node *sib,*kid; } *read_node(); int debug,lvl; char *kptr = ""; char *malloc(); main(argc,argv) char *argv[]; { FILE *ifile; NODE *tnode; if (argc != 2 && argc != 3) { printf("\nSyntax: QA expert_file [debug]\n"); exit(); } debug = (argc == 3); if ((ifile = fopen(argv[1],"r")) == NULL) { printf("Can't open %s for input\n",argv[1]); exit(); } printf("\nOne moment please..."); lvl = 0; tnode = read_node("Y",ifile); /* dummy parent */ fclose(ifile); while (1) ask_node('Y',tnode); } NODE *read_node(sibs,ifile) /* recursive tree loading function */ char *sibs; FILE *ifile; { /* static vars */ static char in_buffer[256],*ip; static int *tp,x; /* recursive vars */ FILE *cfile; NODE *nod; char choices[20]; if (*sibs == '\0') return(NULL); nod = (NODE *) malloc(sizeof(NODE)); nod->nam = *sibs; nod->typ = '\0'; nod->kid = nod->sib = NULL; if (debug) { for (x = 0; x < lvl; ++x) printf(" "); printf("Expecting %c Got ",nod->nam); } tp = (int *) &nod->txt; while (nod->typ == '\0') { *tp = NULL; if ((ip = fgets(in_buffer,255,ifile)) == NULL) { printf("\nUnexpected EOF"); exit(); } while (isspace(*ip)) /* trim leading space */ ip++; switch (*ip) { case '!': /* solution */ nod->typ = *ip; break; case '~': /* auto mutliple choice */ case '>': /* multiple choice */ nod->typ = *ip; trim(ip); strcpy(choices,++ip); ++lvl; nod->kid = read_node(choices,ifile); --lvl; break; case '$': /* chain */ free(nod); trim(ip); ++ip; if ((cfile = fopen(ip,"r")) == NULL) { printf("Can't open subfile %s for input.\n",ip); exit(); } printf("."); nod = read_node("Y",cfile); nod->nam = *sibs; fclose(cfile); break; case '^': /* back track */ nod->typ = *ip; trim(ip); nod->txt = malloc(strlen(ip)); strcpy(nod->txt,++ip); break; case '*': /* comment */ if (debug) fputs(ip,stdout); break; default: /* text */ *tp = (int) malloc(strlen(in_buffer) + 5); strcpy(*tp + 4,in_buffer); tp = (int *) *tp; break; } } nod->sib = read_node(++sibs,ifile); return(nod); } ask_node(chc,nod) /* recursive question/answer function */ char chc; NODE *nod; { static int c,flg; static char tmpstr[17]; if (chc != nod->nam) { if (nod->sib == NULL) return(FALSE); return(ask_node(chc,nod->sib)); } do { if (nod->typ == '^') { kptr = nod->txt; return(FALSE); } if (*kptr == '\0') { switch (nod->typ) { case '!': c = getkey(&nod->txt,"Hit any key to continue: "); break; case '>': c = getkey(&nod->txt,"? "); break; case '~': read_sibs(nod->kid,tmpstr); c = tmpstr[rnd(strlen(tmpstr))]; break; } } else c = *kptr++; switch (c) { case END: case CTRLZ: exit(); case '/': case BS: case LEFT: case UP: return(FALSE); case F1: case HOME: case ESC: return(TRUE); } switch (nod->typ) { case '>': flg = ask_node(c,nod->kid) == FALSE; break; case '~': ask_node(c,nod->kid); /* no break */ default: flg = FALSE; } } while (flg); return(TRUE); } /* * misc functions */ trim(str) /* trim white space */ char *str; { static char *p; p = str + strlen(str) - 1; while (isspace(*p)) *p-- = '\0'; } getkey(tp,str) int *tp; char *str; { static int c; for (; *tp; tp = (int *) *tp) printf(*tp + 4); printf("\n\n"); cprintf(str); c = gchar(); if (isprint(c)) printf("%c",c); printf("\n\n"); return(toupper(c)); } read_sibs(nod,p) NODE *nod; char *p; { if (nod) { *p = nod->nam; read_sibs(nod->sib,++p); } else *p = '\0'; } gchar() { static int c; while (bdos(0x0B,00) == 0) ; if (c = bdos(0x06,0xFF)) return(c); else return(256 | bdos(0x06,0xFF)); } /* random function (could be improved...) */ #define PI 0.141592653897 double seed = 0.0,randomize(); rnd(n) /* return random integer from 0 to n-1 */ { if (seed == 0.0) seed = randomize(); seed *= 7789.0; seed -= (int) seed; return(seed * n); } double randomize() { static char ireg[8] = {0,0x2C,0,0,0,0,0,0}; static char oreg[8]; sysint(0x21,&ireg,&oreg); return(PI + PI / *((int *) &oreg[6])); }