AI-MegaHAL
view release on metacpan or search on metacpan
libmegahal.c view on Meta::CPAN
/*===========================================================================*/
/*
* Copyright (C) 1998 Jason Hutchens
*
* 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 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.,
* 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/* Modified version
Alexandr Ciornii
Win32 (MSVC & gcc), FreeBSD, Mac Darwin compatibility
const'ing function parameters
Craig Andrews
void megahal_learn
*/
/*===========================================================================*/
/*
* $Id: megahal.c,v 1.6 2002/10/16 04:32:53 davidw Exp $
*
* File: megahal.c
*
* Program: MegaHAL
*
* Purpose: To simulate a natural language conversation with a psychotic
* computer. This is achieved by learning from the user's
* input using a third-order Markov model on the word level.
* Words are considered to be sequences of characters separated
* by whitespace and punctuation. Replies are generated
* randomly based on a keyword, and they are scored using
* measures of surprise.
*
* Author: Mr. Jason L. Hutchens (http://www.amristar.com.au/~hutch/)
*
* WWW: http://megahal.sourceforge.net
*
* Compilation Notes
* =================
*
* When compiling, be sure to link with the maths library so that the
* log() function can be found.
*
* On the Macintosh, add the library SpeechLib to your project. It is
* very important that you set the attributes to Import Weak. You can
* do this by selecting the lib and then use Project Inspector from the
* Window menu.
*
* CREDITS
* =======
*
* Amiga (AmigaOS)
* ---------------
* Dag Agren (dagren@ra.abo.fi)
*
* DEC (OSF)
* ---------
* Jason Hutchens (hutch@ciips.ee.uwa.edu.au)
*
* Macintosh
* ---------
* Paul Baxter (pbaxter@assistivetech.com)
* Doug Turner (dturner@best.com)
*
* PC (Linux)
* ----------
* Jason Hutchens (hutch@ciips.ee.uwa.edu.au)
*
* PC (OS/2)
* ---------
* Bjorn Karlowsky (?)
*
* PC (Windows 3.11)
* -----------------
* Jim Crawford (pfister_@hotmail.com)
*
* PC (Windows '95)
* ----------------
* Jason Hutchens (hutch@ciips.ee.uwa.edu.au)
*
* PPC (Linux)
* -----------
* Lucas Vergnettes (Lucasv@sdf.lonestar.org)
*
* SGI (Irix)
* ----------
* Jason Hutchens (hutch@ciips.ee.uwa.edu.au)
*
* Sun (SunOS)
* -----------
* Jason Hutchens (hutch@ciips.ee.uwa.edu.au)
*/
/*===========================================================================*/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#ifndef _MSC_VER
#include <unistd.h>
//#include <getopt.h>
#endif
#if !defined(AMIGA) && !defined(__mac_os) && !defined(__FreeBSD__) && !defined(__APPLE__)
// FreeBSD malloc.h is empty and gives error
// Tested on FreeBSD 5.4
libmegahal.c view on Meta::CPAN
static char *read_input(char *);
static void save_model(char *, MODEL *);
#ifdef __mac_os
static char *strdup(const char *);
#endif
static void upper(char *);
static void write_input(char *);
static void write_output(char *);
#if defined(DOS) || defined(__mac_os)
static void usleep(int);
#endif
#if defined(_MSC_VER) || defined(__MINGW32_VERSION)
#include <windows.h>
#define usleep(i) Sleep(i)
#endif
static char *format_output(char *);
static void free_dictionary(DICTIONARY *);
static void free_model(MODEL *);
static void free_tree(TREE *);
static void free_word(STRING);
static void free_words(DICTIONARY *);
static void initialize_context(MODEL *);
static void initialize_dictionary(DICTIONARY *);
static DICTIONARY *initialize_list(char *);
static SWAP *initialize_swap(char *);
static void load_dictionary(FILE *, DICTIONARY *);
static bool load_model(char *, MODEL *);
static void load_personality(MODEL **);
static void load_tree(FILE *, TREE *);
static void load_word(FILE *, DICTIONARY *);
static DICTIONARY *make_keywords(MODEL *, DICTIONARY *);
static char *make_output(DICTIONARY *);
static MODEL *new_model(int);
static TREE *new_node(void);
static SWAP *new_swap(void);
static bool print_header(FILE *);
static bool progress(char *, int, int);
static DICTIONARY *reply(MODEL *, DICTIONARY *);
static void save_dictionary(FILE *, DICTIONARY *);
static void save_tree(FILE *, TREE *);
static void save_word(FILE *, STRING);
static int search_dictionary(DICTIONARY *, STRING, bool *);
static int search_node(TREE *, int, bool *);
static int seed(MODEL *, DICTIONARY *);
static void show_dictionary(DICTIONARY *);
static void speak(char *);
static bool status(char *, ...);
static void train(MODEL *, char *);
static void typein(char);
static void update_context(MODEL *, int);
static void update_model(MODEL *, int);
static bool warn(char *, char *, ...);
static int wordcmp(STRING, STRING);
static bool word_exists(DICTIONARY *, STRING);
static int rnd(int);
/* Function: setnoprompt
Purpose: Set noprompt variable.
*/
void megahal_setnoprompt(void)
{
noprompt = TRUE;
}
void megahal_setnowrap (void)
{
nowrap = TRUE;
}
void megahal_setnobanner (void)
{
nobanner = TRUE;
}
void megahal_seterrorfile(char *filename)
{
errorfilename = filename;
}
void megahal_setstatusfile(char *filename)
{
statusfilename = filename;
}
/*
megahal_initialize --
Initialize various brains and files.
Results:
None.
*/
void megahal_initialize(void)
{
errorfp = stderr;
statusfp = stdout;
// initialize_error(errorfilename);
// initialize_status(statusfilename);
ignore(0);
#ifdef AMIGA
_AmigaLocale=OpenLocale(NULL);
#endif
#ifdef __mac_os
gSpeechExists = initialize_speech();
#endif
if(!nobanner)
fprintf(stdout,
"+------------------------------------------------------------------------+\n"
"| |\n"
"| # # ###### #### ## # # ## # |\n"
"| ## ## # # # # # # # # # # ### |\n"
"| # ## # ##### # # # ###### # # # # # |\n"
"| # # # # ### ###### # # ###### # # # ### |\n"
"| # # # # # # # # # # # # # # # # |\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;
libmegahal.c view on Meta::CPAN
/*
* Must be called because it does use some system memory
*/
if (gSpeechChannel) {
StopSpeech(gSpeechChannel);
DisposeSpeechChannel(gSpeechChannel);
gSpeechChannel = nil;
}
#endif
exit(0);
}
/*---------------------------------------------------------------------------*/
/*
* Function: Read_Input
*
* Purpose: Read an input string from the user.
*/
char *read_input(char *prompt)
{
static char *input=NULL;
bool finish;
int length;
int c;
/*
* Perform some initializations. The finish boolean variable is used
* to detect a double line-feed, while length contains the number of
* characters in the input string.
*/
finish=FALSE;
length=0;
if(input==NULL) {
input=(char *)malloc(sizeof(char));
if(input==NULL) {
error("read_input", "Unable to allocate the input string");
return(input);
}
}
/*
* Display the prompt to the user.
*/
fprintf(stdout, prompt);
fflush(stdout);
/*
* Loop forever, reading characters and putting them into the input
* string.
*/
while(TRUE) {
/*
* Read a single character from stdin.
*/
c=getc(stdin);
/*
* If the character is a line-feed, then set the finish variable
* to TRUE. If it already is TRUE, then this is a double line-feed,
* in which case we should exit. After a line-feed, display the
* prompt again, and set the character to the space character, as
* we don't permit linefeeds to appear in the input.
*/
if((char)(c)=='\n') {
if(finish==TRUE) break;
fprintf(stdout, prompt);
fflush(stdout);
finish=TRUE;
c=32;
} else {
finish=FALSE;
}
/*
* 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));
}
libmegahal.c view on Meta::CPAN
for(i=0; i<node->branch; ++i) {
node->tree[i]=new_node();
++level;
load_tree(file, node->tree[i]);
--level;
if(level==0) progress(NULL, i, node->branch);
}
if(level==0) progress(NULL, 1, 1);
}
/*---------------------------------------------------------------------------*/
/*
* Function: Load_Model
*
* Purpose: Load a model into memory.
*/
bool load_model(char *filename, MODEL *model)
{
FILE *file;
char cookie[16];
if(filename==NULL) return(FALSE);
file=fopen(filename, "rb");
if(file==NULL) {
warn("load_model", "Unable to open file `%s'", filename);
return(FALSE);
}
fread(cookie, sizeof(char), strlen(COOKIE), file);
if(strncmp(cookie, COOKIE, strlen(COOKIE))!=0) {
warn("load_model", "File `%s' is not a MegaHAL brain", filename);
goto fail;
}
fread(&(model->order), sizeof(BYTE1), 1, file);
load_tree(file, model->forward);
load_tree(file, model->backward);
load_dictionary(file, model->dictionary);
return(TRUE);
fail:
fclose(file);
return(FALSE);
}
/*---------------------------------------------------------------------------*/
/*
* Function: Make_Words
*
* Purpose: Break a string into an array of words.
*/
void make_words(char *input, DICTIONARY *words)
{
int offset=0;
/*
* Clear the entries in the dictionary
*/
free_dictionary(words);
/*
* If the string is empty then do nothing, for it contains no words.
*/
if(strlen(input)==0) return;
/*
* Loop forever.
*/
while(1) {
/*
* If the current character is of the same type as the previous
* character, then include it in the word. Otherwise, terminate
* the current word.
*/
if(boundary(input, offset)) {
/*
* Add the word to the dictionary
*/
if(words->entry==NULL)
words->entry=(STRING *)malloc((words->size+1)*sizeof(STRING));
else
words->entry=(STRING *)realloc(words->entry, (words->size+1)*sizeof(STRING));
if(words->entry==NULL) {
error("make_words", "Unable to reallocate dictionary");
return;
}
words->entry[words->size].length=offset;
words->entry[words->size].word=input;
words->size+=1;
if(offset==(int)strlen(input)) break;
input+=offset;
offset=0;
} else {
++offset;
}
}
/*
* If the last word isn't punctuation, then replace it with a
* full-stop character.
*/
if(isalnum(words->entry[words->size-1].word[0])) {
if(words->entry==NULL)
words->entry=(STRING *)malloc((words->size+1)*sizeof(STRING));
else
words->entry=(STRING *)realloc(words->entry, (words->size+1)*sizeof(STRING));
if(words->entry==NULL) {
error("make_words", "Unable to reallocate dictionary");
return;
}
words->entry[words->size].length=1;
words->entry[words->size].word=".";
++words->size;
}
else if(strchr("!.?", words->entry[words->size-1].word[words->entry[words->size-1].length-1])==NULL) {
words->entry[words->size-1].length=1;
words->entry[words->size-1].word=".";
}
return;
}
/*---------------------------------------------------------------------------*/
/*
* Function: Boundary
*
* Purpose: Return whether or not a word boundary exists in a string
* at the specified location.
*/
bool boundary(char *string, int position)
{
if(position==0)
return(FALSE);
if(position==(int)strlen(string))
return(TRUE);
if(
(string[position]=='\'')&&
(isalpha(string[position-1])!=0)&&
(isalpha(string[position+1])!=0)
)
return(FALSE);
if(
(position>1)&&
(string[position-1]=='\'')&&
(isalpha(string[position-2])!=0)&&
(isalpha(string[position])!=0)
)
return(FALSE);
if(
(isalpha(string[position])!=0)&&
(isalpha(string[position-1])==0)
)
return(TRUE);
if(
(isalpha(string[position])==0)&&
(isalpha(string[position-1])!=0)
)
return(TRUE);
if(isdigit(string[position])!=isdigit(string[position-1]))
return(TRUE);
return(FALSE);
}
/*---------------------------------------------------------------------------*/
/*
* Function: Make_Greeting
*
* Purpose: Put some special words into the dictionary so that the
* program will respond as if to a new judge.
*/
void make_greeting(DICTIONARY *words)
{
register int i;
for(i=0; i<words->size; ++i) free(words->entry[i].word);
free_dictionary(words);
if(grt->size>0) (void)add_word(words, grt->entry[rnd(grt->size)]);
}
/*---------------------------------------------------------------------------*/
/*
* Function: Generate_Reply
*
* Purpose: Take a string of user input and return a string of output
* which may vaguely be construed as containing a reply to
* whatever is in the input string.
*/
char *generate_reply(MODEL *model, DICTIONARY *words)
{
static DICTIONARY *dummy=NULL;
DICTIONARY *replywords;
DICTIONARY *keywords;
float surprise;
float max_surprise;
char *output;
static char *output_none=NULL;
int count;
int basetime;
int timeout = TIMEOUT;
/*
* Create an array of keywords from the words in the user's input
*/
keywords=make_keywords(model, words);
/*
* Make sure some sort of reply exists
*/
if(output_none==NULL) {
output_none=malloc(40);
if(output_none!=NULL)
strcpy(output_none, "I don't know enough to answer you yet!");
}
output=output_none;
if(dummy == NULL) dummy = new_dictionary();
replywords = reply(model, dummy);
if(dissimilar(words, replywords) == TRUE) output = make_output(replywords);
/*
* Loop for the specified waiting period, generating and evaluating
* replies
*/
max_surprise=(float)-1.0;
count=0;
basetime=time(NULL);
/* progress("Generating reply", 0, 1); */
do {
replywords=reply(model, keywords);
surprise=evaluate_reply(model, keywords, replywords);
++count;
if((surprise>max_surprise)&&(dissimilar(words, replywords)==TRUE)) {
max_surprise=surprise;
output=make_output(replywords);
}
/* progress(NULL, (time(NULL)-basetime),timeout); */
} while((time(NULL)-basetime)<timeout);
progress(NULL, 1, 1);
/*
* Return the best answer we generated
*/
return(output);
}
/*---------------------------------------------------------------------------*/
/*
* Function: Dissimilar
*
* Purpose: Return TRUE or FALSE depending on whether the dictionaries
* are the same or not.
*/
bool dissimilar(DICTIONARY *words1, DICTIONARY *words2)
{
register int i;
if(words1->size!=words2->size) return(TRUE);
for(i=0; i<words1->size; ++i)
if(wordcmp(words1->entry[i], words2->entry[i])!=0) return(TRUE);
return(FALSE);
}
/*---------------------------------------------------------------------------*/
/*
* Function: Make_Keywords
*
* Purpose: Put all the interesting words from the user's input into
* a keywords dictionary, which will be used when generating
* a reply.
*/
DICTIONARY *make_keywords(MODEL *model, DICTIONARY *words)
{
static DICTIONARY *keys=NULL;
register int i;
register int j;
int c;
if(keys==NULL) keys=new_dictionary();
for(i=0; i<keys->size; ++i) free(keys->entry[i].word);
free_dictionary(keys);
for(i=0; i<words->size; ++i) {
/*
* Find the symbol ID of the word. If it doesn't exist in
* the model, or if it begins with a non-alphanumeric
* character, or if it is in the exclusion array, then
* skip over it.
*/
c=0;
for(j=0; j<swp->size; ++j)
if(wordcmp(swp->from[j], words->entry[i])==0) {
add_key(model, keys, swp->to[j]);
++c;
}
if(c==0) add_key(model, keys, words->entry[i]);
libmegahal.c view on Meta::CPAN
/*
* Function: Speak
*/
void speak(char *output)
{
if(speech==FALSE) return;
#ifdef __mac_os
if(gSpeechExists) {
OSErr err;
if (gSpeechChannel)
err = SpeakText(gSpeechChannel, output, strlen(output));
else {
c2pstr(output);
SpeakString((StringPtr)output);
p2cstr((StringPtr)output);
}
}
#endif
}
/*---------------------------------------------------------------------------*/
/*
* Function: Progress
*
* Purpose: Display a progress indicator as a percentage.
*/
bool progress(char *message, int done, int total)
{
static int last=0;
static bool first=FALSE;
/*
* We have already hit 100%, and a newline has been printed, so nothing
* needs to be done.
*/
if((done*100/total==100)&&(first==FALSE)) return(TRUE);
/*
* Nothing has changed since the last time this function was called,
* so do nothing, unless it's the first time!
*/
if(done*100/total==last) {
if((done==0)&&(first==FALSE)) {
// fprintf(stderr, "%s: %3d%%", message, done*100/total);
first=TRUE;
}
return(TRUE);
}
/*
* Erase what we printed last time, and print the new percentage.
*/
last=done*100/total;
//if(done>0) fprintf(stderr, "%c%c%c%c", 8, 8, 8, 8);
//fprintf(stderr, "%3d%%", done*100/total);
/*
* We have hit 100%, so reset static variables and print a newline.
*/
if(last==100) {
first=FALSE;
last=0;
//fprintf(stderr, "\n");
}
return(TRUE);
}
/*---------------------------------------------------------------------------*/
void help(void)
{
int j;
for(j=0; j<COMMAND_SIZE; ++j) {
printf("#%-7s: %s\n", command[j].word.word, command[j].helpstring);
}
}
/*---------------------------------------------------------------------------*/
void load_personality(MODEL **model)
{
FILE *file;
static char *filename=NULL;
if(filename==NULL) filename=(char *)malloc(sizeof(char)*1);
/*
* Allocate memory for the filename
*/
filename=(char *)realloc(filename,
sizeof(char)*(strlen(directory)+strlen(SEP)+12));
if(filename==NULL) error("load_personality","Unable to allocate filename");
/*
* Check to see if the brain exists
*/
if(strcmp(directory, DEFAULT)!=0) {
sprintf(filename, "%s%smegahal.brn", directory, SEP);
file=fopen(filename, "r");
if(file==NULL) {
sprintf(filename, "%s%smegahal.trn", directory, SEP);
file=fopen(filename, "r");
if(file==NULL) {
fprintf(stdout, "Unable to change MegaHAL personality to \"%s\".\n"
"Reverting to MegaHAL personality \"%s\".\n", directory, last);
free(directory);
directory=strdup(last);
return;
}
}
fclose(file);
fprintf(stdout, "Changing to MegaHAL personality \"%s\".\n", directory);
}
/*
* Free the current personality
*/
free_model(*model);
free_words(ban);
free_dictionary(ban);
free_words(aux);
free_dictionary(aux);
free_words(grt);
free_dictionary(grt);
free_swap(swp);
/*
* Create a language model.
*/
*model=new_model(order);
/*
* Train the model on a text if one exists
*/
sprintf(filename, "%s%smegahal.brn", directory, SEP);
if(load_model(filename, *model)==FALSE) {
sprintf(filename, "%s%smegahal.trn", directory, SEP);
train(*model, filename);
}
/*
* Read a dictionary containing banned keywords, auxiliary keywords,
* greeting keywords and swap keywords
*/
sprintf(filename, "%s%smegahal.ban", directory, SEP);
ban=initialize_list(filename);
sprintf(filename, "%s%smegahal.aux", directory, SEP);
aux=initialize_list(filename);
sprintf(filename, "%s%smegahal.grt", directory, SEP);
grt=initialize_list(filename);
sprintf(filename, "%s%smegahal.swp", directory, SEP);
swp=initialize_swap(filename);
}
/*---------------------------------------------------------------------------*/
void change_personality(DICTIONARY *command, int position, MODEL **model)
{
if(directory == NULL) {
directory = (char *)malloc(sizeof(char)*(strlen(DEFAULT)+1));
if(directory == NULL) {
error("change_personality", "Unable to allocate directory");
} else {
strcpy(directory, DEFAULT);
}
}
if(last == NULL) {
last = strdup(directory);
}
if((command == NULL)||((position+2)>=command->size)) {
/* no dir set, so we leave it to whatever was set above */
} else {
directory=(char *)realloc(directory,
sizeof(char)*(command->entry[position+2].length+1));
if(directory == NULL)
error("change_personality", "Unable to allocate directory");
strncpy(directory, command->entry[position+2].word,
command->entry[position+2].length);
directory[command->entry[position+2].length]='\0';
}
load_personality(model);
}
/*---------------------------------------------------------------------------*/
void free_words(DICTIONARY *words)
{
unsigned int i;
if(words == NULL) return;
if(words->entry != NULL)
for(i=0; i<words->size; ++i) free_word(words->entry[i]);
}
/*---------------------------------------------------------------------------*/
void free_word(STRING word)
{
free(word.word);
}
/*===========================================================================*/
/*
* $Log: megahal.c,v $
* Revision 1.6 2002/10/16 04:32:53 davidw
* * megahal.c (change_personality): [ 541667 ] Added patch from Andrew
* Burke to rework logic in change_personality.
*
* * megahal.c: Trailing white space cleanup.
*
* * python-interface.c: [ 546397 ] Change python include path to a more
* recent version. This should really be fixed by using some of
* Python's build automation tools.
*
* Revision 1.5 2000/11/08 11:07:11 davidw
* Moved README to docs directory.
*
* Changes to debian files.
*
* Revision 1.4 2000/09/07 21:51:12 davidw
* Created some library functions that I think are workable, and moved
* everything else into megahal.c as static variables/functions.
*
* Revision 1.3 2000/09/07 11:43:43 davidw
* Started hacking:
*
* Reduced makefile targets, eliminating non-Linux OS's. There should be
* a cleaner way to do this.
( run in 0.978 second using v1.01-cache-2.11-cpan-39bf76dae61 )