AI-MegaHAL
view release on metacpan or search on metacpan
libmegahal.c view on Meta::CPAN
"| # # # # # # # # # # # # # # # # |\n"
"| # # ###### #### # # # # # # ###### # ###r6 |\n"
"| |\n"
"| Copyright(C) 1998 Jason Hutchens |\n"
"+------------------------------------------------------------------------+\n"
);
words = new_dictionary();
greets = new_dictionary();
change_personality(NULL, 0, &model);
}
/*
megahal_do_reply --
Take string as input, and return allocated string as output. The
user is responsible for freeing this memory.
*/
char *megahal_do_reply(char *input, int log)
{
char *output = NULL;
if (log != 0)
write_input(input); /* log input if so desired */
upper(input);
make_words(input, words);
learn(model, words);
output = generate_reply(model, words);
capitalize(output);
return output;
}
/*
megahal_learn --
Take string as input and and learn with no output.
*/
void megahal_learn(char *input, int log)
{
if (log != 0)
write_input(input); /* log input if so desired */
upper(input);
make_words(input, words);
learn(model, words);
}
/*
megahal_initial_greeting --
This function returns an initial greeting. It can be used to start
Megahal conversations, but it isn't necessary.
*/
char *megahal_initial_greeting(void)
{
char *output;
make_greeting(greets);
output = generate_reply(model, greets);
return output;
}
/*
megahal_output --
This function pretty prints output.
Wrapper function to have things in the right namespace.
*/
void megahal_output(char *output)
{
if(!quiet)
write_output(output);
}
/*
megahal_input --
Get a string from stdin, using a prompt.
*/
char *megahal_input(char *prompt)
{
if (noprompt)
return read_input("");
else
return read_input(prompt);
}
/*
megahal_command --
Check to see if input is a megahal command, and if so, act upon it.
Returns 1 if it is a command, 0 if it is not.
*/
int megahal_command(char *input)
{
int position = 0;
char *output;
make_words(input,words);
switch(execute_command(words, &position)) {
case EXIT:
libmegahal.c view on Meta::CPAN
/*
* Re-allocate the input string so that it can hold one more
* character.
*/
++length;
input=(char *)realloc((char *)input,sizeof(char)*(length+1));
if(input==NULL) {
error("read_input", "Unable to re-allocate the input string");
return(NULL);
}
/*
* Add the character just read to the input string.
*/
input[length-1]=(char)c;
input[length]='\0';
}
while(isspace(input[length-1])) --length;
input[length]='\0';
/*
* We have finished, so return the input string.
*/
return(input);
}
/*---------------------------------------------------------------------------*/
/*
* Function: Initialize_Error
*
* Purpose: Close the current error file pointer, and open a new one.
*/
bool initialize_error(char *filename)
{
if(errorfp!=stderr) fclose(errorfp);
if(filename==NULL) return(TRUE);
errorfp = fopen(filename, "a");
if(errorfp==NULL) {
errorfp=stderr;
return(FALSE);
}
return(print_header(errorfp));
}
/*---------------------------------------------------------------------------*/
/*
* Function: Error
*
* Purpose: Print the specified message to the error file.
*/
void error(char *title, char *fmt, ...)
{
va_list argp;
fprintf(errorfp, "%s: ", title);
va_start(argp, fmt);
vfprintf(errorfp, fmt, argp);
va_end(argp);
fprintf(errorfp, ".\n");
fflush(errorfp);
// fprintf(stderr, "MegaHAL died for some reason; check the error log.\n");
exit(1);
}
/*---------------------------------------------------------------------------*/
bool warn(char *title, char *fmt, ...)
{
va_list argp;
fprintf(errorfp, "%s: ", title);
va_start(argp, fmt);
vfprintf(errorfp, fmt, argp);
va_end(argp);
fprintf(errorfp, ".\n");
fflush(errorfp);
// fprintf(stderr, "MegaHAL emitted a warning; check the error log.\n");
return(TRUE);
}
/*---------------------------------------------------------------------------*/
/*
* Function: Initialize_Status
*
* Purpose: Close the current status file pointer, and open a new one.
*/
bool initialize_status(char *filename)
{
if(statusfp!=stdout) fclose(statusfp);
if(filename==NULL) return(FALSE);
statusfp=fopen(filename, "a");
if(statusfp==NULL) {
statusfp=stdout;
return(FALSE);
}
return(print_header(statusfp));
}
/*---------------------------------------------------------------------------*/
/*
* Function: Status
*
* Purpose: Print the specified message to the status file.
*/
bool status(char *fmt, ...)
{
va_list argp;
va_start(argp, fmt);
vfprintf(statusfp, fmt, argp);
va_end(argp);
fflush(statusfp);
return(TRUE);
}
/*---------------------------------------------------------------------------*/
/*
* Function: Print_Header
*
* Purpose: Display a copyright message and timestamp.
*/
bool print_header(FILE *file)
{
time_t clock;
char timestamp[1024];
struct tm *local;
clock=time(NULL);
local=localtime(&clock);
strftime(timestamp, 1024, "Start at: [%Y/%m/%d %H:%M:%S]\n", local);
fprintf(file, "MegaHALv8\n");
fprintf(file, "Copyright (C) 1998 Jason Hutchens\n");
fprintf(file, timestamp);
fflush(file);
return(TRUE);
}
/*---------------------------------------------------------------------------*/
/*
* Function: Write_Output
*
* Purpose: Display the output string.
*/
void write_output(char *output)
{
char *formatted;
char *bit;
capitalize(output);
speak(output);
width=75;
formatted=format_output(output);
delay(formatted);
width=64;
formatted=format_output(output);
bit=strtok(formatted, "\n");
if(bit==NULL) (void)status("MegaHAL: %s\n", formatted);
while(bit!=NULL) {
(void)status("MegaHAL: %s\n", bit);
bit=strtok(NULL, "\n");
}
}
/*---------------------------------------------------------------------------*/
/*
* Function: Capitalize
*
* Purpose: Convert a string to look nice.
*/
void capitalize(char *string)
{
unsigned int i;
bool start=TRUE;
for(i=0; i<(int)strlen(string); ++i) {
if(isalpha(string[i])) {
if(start==TRUE) string[i]=(char)toupper((int)string[i]);
else string[i]=(char)tolower((int)string[i]);
start=FALSE;
}
if((i>2)&&(strchr("!.?", string[i-1])!=NULL)&&(isspace(string[i])))
start=TRUE;
}
}
/*---------------------------------------------------------------------------*/
/*
* Function: Upper
*
* Purpose: Convert a string to its uppercase representation.
*/
void upper(char *string)
{
unsigned int i;
for(i=0; i<(int)strlen(string); ++i) string[i]=(char)toupper((int)string[i]);
}
/*---------------------------------------------------------------------------*/
/*
* Function: Write_Input
*
* Purpose: Log the user's input
*/
void write_input(char *input)
{
char *formatted;
char *bit;
width=64;
formatted=format_output(input);
bit=strtok(formatted, "\n");
if(bit==NULL) (void)status("User: %s\n", formatted);
while(bit!=NULL) {
(void)status("User: %s\n", bit);
bit=strtok(NULL, "\n");
}
}
/*---------------------------------------------------------------------------*/
/*
* Function: Format_Output
*
* Purpose: Format a string to display nicely on a terminal of a given
* width.
*/
static char *format_output(char *output)
{
static char *formatted=NULL;
unsigned int i,j,c;
int l;
if(formatted==NULL) {
formatted=(char *)malloc(sizeof(char));
if(formatted==NULL) {
error("format_output", "Unable to allocate formatted");
return("ERROR");
}
}
libmegahal.c view on Meta::CPAN
}
return(keys);
}
/*---------------------------------------------------------------------------*/
/*
* Function: Add_Key
*
* Purpose: Add a word to the keyword dictionary.
*/
void add_key(MODEL *model, DICTIONARY *keys, STRING word)
{
int symbol;
symbol=find_word(model->dictionary, word);
if(symbol==0) return;
if(isalnum(word.word[0])==0) return;
symbol=find_word(ban, word);
if(symbol!=0) return;
symbol=find_word(aux, word);
if(symbol!=0) return;
add_word(keys, word);
}
/*---------------------------------------------------------------------------*/
/*
* Function: Add_Aux
*
* Purpose: Add an auxilliary keyword to the keyword dictionary.
*/
void add_aux(MODEL *model, DICTIONARY *keys, STRING word)
{
int symbol;
symbol=find_word(model->dictionary, word);
if(symbol==0) return;
if(isalnum(word.word[0])==0) return;
symbol=find_word(aux, word);
if(symbol==0) return;
add_word(keys, word);
}
/*---------------------------------------------------------------------------*/
/*
* Function: Reply
*
* Purpose: Generate a dictionary of reply words appropriate to the
* given dictionary of keywords.
*/
DICTIONARY *reply(MODEL *model, DICTIONARY *keys)
{
static DICTIONARY *replies=NULL;
register int i;
int symbol;
bool start=TRUE;
if(replies==NULL) replies=new_dictionary();
free_dictionary(replies);
/*
* Start off by making sure that the model's context is empty.
*/
initialize_context(model);
model->context[0]=model->forward;
used_key=FALSE;
/*
* Generate the reply in the forward direction.
*/
while(TRUE) {
/*
* Get a random symbol from the current context.
*/
if(start==TRUE) symbol=seed(model, keys);
else symbol=babble(model, keys, replies);
if((symbol==0)||(symbol==1)) break;
start=FALSE;
/*
* Append the symbol to the reply dictionary.
*/
if(replies->entry==NULL)
replies->entry=(STRING *)malloc((replies->size+1)*sizeof(STRING));
else
replies->entry=(STRING *)realloc(replies->entry, (replies->size+1)*sizeof(STRING));
if(replies->entry==NULL) {
error("reply", "Unable to reallocate dictionary");
return(NULL);
}
replies->entry[replies->size].length=
model->dictionary->entry[symbol].length;
replies->entry[replies->size].word=
model->dictionary->entry[symbol].word;
replies->size+=1;
/*
* Extend the current context of the model with the current symbol.
*/
update_context(model, symbol);
}
/*
* Start off by making sure that the model's context is empty.
*/
initialize_context(model);
model->context[0]=model->backward;
/*
* Re-create the context of the model from the current reply
* dictionary so that we can generate backwards to reach the
* beginning of the string.
*/
if(replies->size>0) for(i=MIN(replies->size-1, model->order); i>=0; --i) {
symbol=find_word(model->dictionary, replies->entry[i]);
update_context(model, symbol);
}
/*
* Generate the reply in the backward direction.
*/
while(TRUE) {
/*
* Get a random symbol from the current context.
*/
symbol=babble(model, keys, replies);
if((symbol==0)||(symbol==1)) break;
/*
* Prepend the symbol to the reply dictionary.
*/
if(replies->entry==NULL)
replies->entry=(STRING *)malloc((replies->size+1)*sizeof(STRING));
else
replies->entry=(STRING *)realloc(replies->entry, (replies->size+1)*sizeof(STRING));
if(replies->entry==NULL) {
error("reply", "Unable to reallocate dictionary");
libmegahal.c view on Meta::CPAN
/*---------------------------------------------------------------------------*/
/*
* Function: Make_Output
*
* Purpose: Generate a string from the dictionary of reply words.
*/
char *make_output(DICTIONARY *words)
{
static char *output=NULL;
register int i;
register int j;
int length;
static char *output_none=NULL;
if(output_none==NULL) output_none=malloc(40);
if(output==NULL) {
output=(char *)malloc(sizeof(char));
if(output==NULL) {
error("make_output", "Unable to allocate output");
return(output_none);
}
}
if(words->size==0) {
if(output_none!=NULL)
strcpy(output_none, "I am utterly speechless!");
return(output_none);
}
length=1;
for(i=0; i<words->size; ++i) length+=words->entry[i].length;
output=(char *)realloc(output, sizeof(char)*length);
if(output==NULL) {
error("make_output", "Unable to reallocate output.");
if(output_none!=NULL)
strcpy(output_none, "I forgot what I was going to say!");
return(output_none);
}
length=0;
for(i=0; i<words->size; ++i)
for(j=0; j<words->entry[i].length; ++j)
output[length++]=words->entry[i].word[j];
output[length]='\0';
return(output);
}
/*---------------------------------------------------------------------------*/
/*
* Function: Babble
*
* Purpose: Return a random symbol from the current context, or a
* zero symbol identifier if we've reached either the
* start or end of the sentence. Select the symbol based
* on probabilities, favouring keywords. In all cases,
* use the longest available context to choose the symbol.
*/
int babble(MODEL *model, DICTIONARY *keys, DICTIONARY *words)
{
TREE *node;
register int i;
int count;
int symbol;
/*
* Select the longest available context.
*/
for(i=0; i<=model->order; ++i)
if(model->context[i]!=NULL)
node=model->context[i];
if(node->branch==0) return(0);
/*
* Choose a symbol at random from this context.
*/
i=rnd(node->branch);
count=rnd(node->usage);
while(count>=0) {
/*
* If the symbol occurs as a keyword, then use it. Only use an
* auxilliary keyword if a normal keyword has already been used.
*/
symbol=node->tree[i]->symbol;
if(
(find_word(keys, model->dictionary->entry[symbol])!=0)&&
((used_key==TRUE)||
(find_word(aux, model->dictionary->entry[symbol])==0))&&
(word_exists(words, model->dictionary->entry[symbol])==FALSE)
) {
used_key=TRUE;
break;
}
count-=node->tree[i]->count;
i=(i>=(node->branch-1))?0:i+1;
}
return(symbol);
}
/*---------------------------------------------------------------------------*/
/*
* Function: Word_Exists
*
* Purpose: A silly brute-force searcher for the reply string.
*/
bool word_exists(DICTIONARY *dictionary, STRING word)
{
register int i;
for(i=0; i<dictionary->size; ++i)
if(wordcmp(dictionary->entry[i], word)==0)
libmegahal.c view on Meta::CPAN
*
* Revision 1.18 1998/04/06 08:02:01 hutch
* Added debugging stuff, courtesy of Paul Baxter.
*
* Revision 1.17 1998/04/02 01:34:20 hutch
* Added the help function and fixed a few errors.
*
* Revision 1.16 1998/04/01 05:42:57 hutch
* Incorporated Mac code, including speech synthesis, and attempted
* to tidy up the code for multi-platform support.
*
* Revision 1.15 1998/03/27 03:43:15 hutch
* Added AMIGA specific changes, thanks to Dag Agren.
*
* Revision 1.14 1998/02/20 06:40:13 hutch
* Tidied up transcript file format.
*
* Revision 1.13 1998/02/20 06:26:19 hutch
* Fixed random number generator and Seed() function (thanks to Mark
* Tarrabain), removed redundant code left over from the Loebner entry,
* prettied things up a little and destroyed several causes of memory
* leakage (although probably not all).
*
* Revision 1.12 1998/02/04 02:55:11 hutch
* Fixed up memory allocation error which caused SunOS versions to crash.
*
* Revision 1.11 1998/01/22 03:16:30 hutch
* Fixed several memory leaks, and the frustrating bug in the
* Write_Input routine.
*
* Revision 1.10 1998/01/19 06:44:36 hutch
* Fixed MegaHAL to compile under Linux with a small patch credited
* to Joey Hess (joey@kitenet.net). MegaHAL may now be included as
* part of the Debian Linux distribution.
*
* Revision 1.9 1998/01/19 06:37:32 hutch
* Fixed a minor bug with end-of-sentence punctuation.
*
* Revision 1.8 1997/12/24 03:17:01 hutch
* More bug fixes, and hopefully the final contest version!
*
* Revision 1.7 1997/12/22 13:18:09 hutch
* A few more bug fixes, and non-repeating implemented.
*
* Revision 1.6 1997/12/22 04:27:04 hutch
* A few minor bug fixes.
*
* Revision 1.5 1997/12/15 04:35:59 hutch
* Final Loebner version!
*
* Revision 1.4 1997/12/11 05:45:29 hutch
* The almost finished version.
*
* Revision 1.3 1997/12/10 09:08:09 hutch
* Now Loebner complient (tm).
*
* Revision 1.2 1997/12/08 06:22:32 hutch
* Tidied up.
*
* Revision 1.1 1997/12/05 07:11:44 hutch
* Initial revision (lots of files were merged into one, RCS re-started)
*
* Revision 1.7 1997/12/04 07:07:13 hutch
* Added load and save functions, and tidied up some code.
*
* Revision 1.6 1997/12/02 08:34:47 hutch
* Added the ban, aux and swp functions.
*
* Revision 1.5 1997/12/02 06:03:04 hutch
* Updated to use a special terminating symbol, and to store only
* branches of maximum depth, as they are the only ones used in
* the reply.
*
* Revision 1.4 1997/10/28 09:23:12 hutch
* MegaHAL is babbling nicely, but without keywords.
*
* Revision 1.3 1997/10/15 09:04:03 hutch
* MegaHAL can parrot back whatever the user says.
*
* Revision 1.2 1997/07/21 04:03:28 hutch
* Fully working.
*
* Revision 1.1 1997/07/15 01:55:25 hutch
* Initial revision.
*/
/*===========================================================================*/
( run in 1.315 second using v1.01-cache-2.11-cpan-39bf76dae61 )