Program Eliza; { Command line: Eliza_tp [path of Eliza.dat] The one command line parameter (optional) is the path where the file Eliza.dat can be found if not that file is not in the directory where Eliza_tp.exe is found. } {$IFDEF Print} Uses CRT, Printer; {$ELSE} Uses CRT; {$ENDIF} Const Key_Rec_headr_len = 12; Reply_rec_headr_len = 5; Type Key_Ptr = ^Key_rec; {Pointer to key record } Reply_Ptr = ^Reply_rec; Key_rec = record { Key record stores the key phrase } Next_key_rec : Key_Ptr; { 4 bytes } P_flag : byte; { 1 byte } Numb_Reply : integer; { 2 bytes } Reply_list : Reply_Ptr; { 4 bytes } Key_Str : String[16]; { 1 byte (length) } end; { 12 bytes in header } Reply_rec = record { Reply record stores a response to the key phrase } Next_reply_rec : Reply_Ptr; { 4 bytes } Reply_Str : String[120]; { 1 byte (length) } end; { 5 bytes in header } MaxStr = String[80]; Max_Dat_Len = String[120]; Const Top_Key : Key_Ptr = Nil; { Initialize Pointer } First_Key : Key_Ptr = Nil; { Initialize Pointer } Curr_Key : Key_Ptr = Nil; { Initialize Pointer } First_Reply : Reply_Ptr = Nil; { Initialize Pointer } Curr_Reply : Reply_Ptr = Nil; { Initialize Pointer } Number_Replies : Integer = 0; { Initialize count } P_Flag : Byte = 1; { Initialize } FileName : String = 'Eliza.dat'; { Name of data file } Var s : Max_Dat_Len; Input_String : MaxStr; Rest_of_Input : MaxStr; Old_Inp_Str : MaxStr; Pronouns : Array[1..19,1..2] of String[12]; K_Last : Boolean; (*************************************************************************) Procedure Store_Key_Record(var Curr_Key : Key_ptr); { Store a key phrase into the threaded list of key phrases, to conserve memory the length of the key phrase is determined and only enough memory to store the phrase is obtained. } Var PktLen, Strlen : Integer; Prev_Key : Key_ptr; Begin Strlen := ord(s[0]); { Get Length of string} PktLen := StrLen + Key_rec_headr_len; { Calculate size of packet } Prev_Key := Curr_Key; { Save Pointer to previous } GetMem(Curr_Key,PktLen); { Get Memory of PktLen Size } If Top_Key = Nil then begin Top_Key := Curr_Key; { Base of threaded list } First_Key := Curr_key; { First of this set of keys} Prev_Key := Nil; { Initialize Prev_Key pointer } end else begin if First_key = Nil then First_Key := Curr_key; { Start New Set of Keys} end; Curr_Key^.Key_Str := Copy(s,1,StrLen); { Copy string to packet } Prev_Key^.Next_key_Rec := Curr_Key; Curr_Key^.Next_key_Rec := Nil; Curr_key^.Numb_Reply := 0; Curr_key^.P_Flag := 1; Curr_Key^.Reply_list := Nil; End; { End Store_Key_Record } (*************************************************************************) Procedure Store_Reply_Record(var Curr_Reply : Reply_ptr); { Key phrases are grouped together followed by replies to those phrases. For each group of key phrase there is one or more replies. The possible replies to a key phrase are threaded to the packet on the threaded list of key phrases. key1 | \ / key2 ----> reply_a | | | \ / | reply_b | | | \ / | reply_c | | | \ / | etc.; thread of all replies to key1 \ / key3 ----> reply_d | | | \ / | etc.; thread of all replies to key2 \ / etc.; thread of all key phrase groups } Var PktLen, Strlen : Integer; Prev_Reply : Reply_ptr; Begin Strlen := ord(s[0]); { Get Length of string} PktLen := StrLen + Reply_rec_headr_len; { Calculate size of packet } Prev_Reply := Curr_Reply; { Save Pointer to previous } GetMem(Curr_Reply,PktLen); { Get Memory of PktLen Size } If (Top_Key = Nil) or (First_Key = Nil) then begin Writeln('* * * E R R O R * * * Reply found before first key') end else begin if First_Reply = Nil then First_Reply := Curr_Reply; { Start New Set of Keys} end; Curr_Reply^.Reply_Str := Copy(s,1,StrLen); { Copy string to packet } Prev_Reply^.Next_Reply_Rec := Curr_Reply; Curr_Reply^.Next_Reply_Rec := Nil; Number_Replies := Number_Replies + 1; End; { End of Store_Reply_Record } (*************************************************************************) Procedure Thread_Reply; { Thread Replies to Keys } Var Curr_Ptr : Key_Ptr; Begin Curr_Ptr := First_Key; { Point to First Key } While Curr_Ptr <> Nil do Begin Curr_Ptr^.Numb_Reply := Number_Replies; Curr_Ptr^.Reply_list := First_Reply; Curr_Ptr := Curr_Ptr^.Next_key_rec; Curr_Ptr^.P_Flag := P_Flag; End; First_Reply := Nil; Number_Replies := 0; First_Key := Nil; End; (*************************************************************************) Procedure Input_data; { Reads the file Eliza.dat which contains the key phrases and the replies to those key phrases. This file is an ASCII file. Each key phrase must be immediately followed by all possible replies to the phrase. The field ID identies the "type" of the record. There four types, see case statement below, (note that an cross-hatch allows you to put whitespace and comments in the file. } Var InputFile : Text; ID : char; b : char; { Char to skip blank when reading } IOCode, Len : Integer; Path : String; Begin; Path := ParamStr(1); { 1 parameter is expected: the path where Eliza.dat will be found } FileName := Path + FileName; { Concatenate Path and File Name } {$IFDEF Test} Writeln('Path = ',Path, ' File Name = ',FileName); {$ENDIF} Assign(InputFile,FileName); { Assign input file name } Reset(InputFile); { Prepare InputFile to be read } IOCode := IOresult; { Save the return code } if IOCode = 0 then begin While Not EOF(Inputfile) do { Read Until End of File } begin Readln(InputFile,s); { Read Data }; If IOResult <> 0 then Writeln('I/O Error, code = ',IOResult); ID := s[1]; Len := Length(s); If Len > 2 then s := Copy(s, 3, Len-2); If s[1] <> ' ' Then s := ' ' + s; { add a leading blank } If s[Length(s)] <> ' ' Then s := s + ' '; { add a trailing blank } case ID of 'K' : begin If First_Reply <> Nil Then Thread_Reply; Store_Key_Record(Curr_Key); { Call Procedure to store key } end; 'R' : Store_Reply_Record(Curr_Reply); { Call procedure to store reply } 'P' : P_Flag := 0; '#' : else Writeln('Error reading Eliza.dat, invalid record ID'); Writeln('RECORD: ',s); end; end; { End of while } If IOResult <> 0 then Writeln('I/O Error, code = ',IOResult); If First_Reply <> Nil Then Thread_Reply; end; end; (*************************************************************************) Function UpCaseStr(s : MaxStr) : MaxStr; { Convert String to all uppper Case } Var i,j : Integer; Begin j := Ord(s[0]); For i := 1 to j Do s[i] := Upcase(s[i]); UpCaseStr := s; End; (*************************************************************************) Procedure Replace_Pronouns(var Rest_of_Input : MaxStr); Const Init : Boolean = True; Var i, l, L1 ,Len : integer; Str : MaxStr; Begin If Init then Begin { all pronouns in column 1 must all pronouns in column 2 must be UPPER CASE be lower case ------------- ------------- column 1 pronouns are replace by the column 2 entry } Pronouns[1,1] := 'MY SELF'; Pronouns[1,2] := 'your self'; Pronouns[2,1] := 'YOURSELF'; Pronouns[2,2] := 'my self'; Pronouns[3,1] := 'YOURSELVES'; Pronouns[3,2] := 'ourselves'; Pronouns[4,1] := 'OURSELVES'; Pronouns[4,2] := 'yourselves'; Pronouns[5,1] := ' YOU ARE '; Pronouns[5,2] := ' i am '; Pronouns[6,1] := ' I AM '; Pronouns[6,2] := ' you are '; Pronouns[7,1] := ' WERE '; Pronouns[7,2] := ' was '; Pronouns[8,1] := ' WAS '; Pronouns[8,2] := ' were '; Pronouns[9,1] := ' YOUR '; Pronouns[9,2] := ' my '; Pronouns[10,1] := ' I''VE '; Pronouns[10,2] := ' you''ve '; Pronouns[11,1] := ' YOU''VE '; Pronouns[11,2] := ' i''ve '; Pronouns[12,1] := ' I''M'; Pronouns[12,2] := ' you''re '; Pronouns[13,1] := ' YOU''RE '; Pronouns[13,2] := ' i''m '; Pronouns[14,1] := ' AM '; Pronouns[14,2] := ' are '; Pronouns[15,1] := ' ARE '; Pronouns[15,2] := ' am '; Pronouns[16,1] := ' I '; Pronouns[16,2] := ' you '; Pronouns[17,1] := ' YOU '; Pronouns[17,2] := ' i '; Pronouns[18,1] := ' ME '; Pronouns[18,2] := ' you '; Pronouns[19,1] := ' MY '; Pronouns[19,2] := ' your '; Init := False; End; For i := 1 to 19 Do Begin Len := Length(Pronouns[i,1]); L := Pos(Pronouns[i,1],Rest_of_Input); While L <> 0 Do Begin Delete(Rest_of_Input,L,Len); { Delete the Pronouns} Insert(Pronouns[i,2],Rest_of_Input,L); L := Pos(Pronouns[i,1],Rest_of_Input); End; End; Delete(Str,1,1); Rest_of_Input := UpCaseStr(Rest_of_Input); End; (*************************************************************************) Procedure Find_key( Input_String : MaxStr;var Curr_Ptr : Key_Ptr; var K_Last : Boolean); { Find Keys } Var Prev_Ptr : Key_Ptr; L,Start,Len : Integer; Begin L := 0; Prev_Ptr := Nil; Curr_Ptr := Top_Key; { Point to First Reply } While (Curr_Ptr <> Nil) and (L = 0) do Begin L := Pos(Curr_Ptr^.Key_str,Input_String); If L <> 0 then Begin Len := Length(Input_String) - L - 1; Start := L + Length(Curr_Ptr^.Key_str); { copy rest of string, but not space } Rest_of_Input := Copy(Input_String,Start,Len); Prev_Ptr := Curr_Ptr; Curr_Ptr := Nil; { Force exit from loop } End else Begin Prev_Ptr := Curr_Ptr; Curr_Ptr := Curr_Ptr^.Next_key_rec; End; End; { end of while } If Curr_Ptr = Nil then Curr_Ptr := Prev_Ptr; K_Last := False; If (Pos(Curr_Ptr^.Key_Str,'Klast') <> 0) then K_Last := True; End; (*************************************************************************) Procedure Build_Reply(Input_String : MaxStr; var Numb_Replies : Integer; var P_Flag : Byte); Const Ast : String[1] = '*'; Blank : String[1] = ' '; Var I, Rand, L : Integer; RRand : Real; Curr_Reply_Ptr : Reply_Ptr; Curr_Ptr : Key_Ptr; Begin Rest_of_Input := Input_String; RRand := Random; { Generate a Random number } If P_Flag = 82 then Find_Key('KY', Curr_Ptr, K_Last) Else If (RRand < 0.03) then Find_Key('KZ', Curr_Ptr, K_Last) { 3% of the time pick KZ key } Else Find_Key(Input_String, Curr_Ptr, K_Last); Rand := Random(Curr_Ptr^.Numb_Reply); Numb_Replies := Curr_Ptr^.Numb_Reply; Curr_Reply_Ptr := Curr_Ptr^.Reply_list; { Loop to reply selected by random number } If Rand <> 0 then Begin For I := 1 to Rand Do Curr_Reply_Ptr := Curr_Reply_Ptr^.Next_Reply_Rec; End; s := Curr_Reply_Ptr^.Reply_str; { Save Reply } If P_Flag = 82 then Rest_of_Input := Input_String; L := Length(Rest_of_Input); If L > 0 then if (Rest_of_Input[L-1] = '?') or (Rest_of_Input[L-1] = '.') then Delete(Rest_of_Input,L-1,1); { Delete the punctuation } { Does the reply contain an asterisk? } L := Pos(Ast,s); If L <> 0 Then Begin Delete(s,L-1,2); { Delete the * } L := L - 1; { Adjust for deletions } If ((Length(s) - L) >= 0) then Begin if (Rest_of_Input[1] <> ' ') then Rest_of_Input := ' ' + Rest_of_Input; if (s[L] = ' ') then Delete(s,L,1); Replace_Pronouns(Rest_of_Input); if Rest_of_Input[Ord(Rest_of_Input[0])] = ' ' then Delete(Rest_of_Input,Ord(Rest_of_Input[0]),1); Insert(Rest_of_Input,s,L); if (s[L+Length(Rest_of_Input)] <> '?') and (s[L+Length(Rest_of_Input)] <> '.') and (s[L+Length(Rest_of_Input)] <> '!') and (s[L+Length(Rest_of_Input)] <> ' ') then Insert(blank,s,L+Length(Rest_of_Input)); { if not punctuation then insert a blank } End; End; P_Flag := Curr_ptr^.P_Flag; {$IFDEF Test} Writeln(s); Writeln('Found key ',Curr_Ptr^.Key_str,' in string '); Writeln(Input_String,' at byte ',L); {$ENDIF} End; (*************************************************************************) { MAIN Routine } Const P1 : Boolean = False; Var Numb_Replies : Integer; Old_s : MaxStr; Save_Input_String : MaxStr; Begin ClrScr; Randomize; { Initialize Seed } {$IFDEF Test} WriteLn(MemAvail, ' bytes available at start'); WriteLn('Largest Free Block ', MaxAvail, ' bytes at start'); {$ENDIF} Input_data; { Read Data and InitializeKey and Reply lists } WriteLn('Hello, I''m your computerized psychiatrist. What is your problem?'); Repeat Old_Inp_Str := Input_String; Readln(Input_String); Input_String := ' ' + UpCaseStr(Input_String) + ' '; If Old_Inp_Str = Input_String then WriteLn('DON''T REPEAT YOURSELF') else If (Pos('BYE',Input_String) = 0) and (Pos('GOODBY',Input_String) = 0) Then Begin Old_s := s; Build_Reply(Input_String, Numb_Replies, P_Flag); If (Numb_Replies > 1) and (s = Old_s) then Build_Reply(Input_String, Numb_Replies, P_Flag); If (Random < 0.25) and (P_Flag <> 0) then Begin P1 := True; Save_Input_String := Input_String; End Else if (K_Last and P1) Then Begin P1 := False; P_Flag := 82; Build_Reply(Save_Input_String, Numb_Replies, P_Flag); End; WriteLn(s); {$IFDEF Print} Writeln(Lst,Input_String); Writeln(Lst,s); {$ENDIF} End; Until (Pos('BYE',Input_String) <> 0) or (Pos('GOODBY',Input_String) <> 0); Writeln('Its been good talking with you. Whenver you feel a need to talk stop by again.'); Delay(1500); {$IFDEF Test} WriteLn(MemAvail, ' bytes available at end'); WriteLn('Largest Free Block ', MaxAvail, ' bytes at end'); {$ENDIF} End.