{$mode delphi} { $HDR$}
{**********************************************************************}
{ Unit archived using Team Coherence                                   }
{ Team Coherence is Copyright 2002 by Quality Software Components      }
{                                                                      }
{ For further information / comments, visit our WEB site at            }
{ http://www.TeamCoherence.com                                         }
{**********************************************************************}
{}
{ $Log:  21828: EZEngine.pas 
{
{   Rev 1.0    2003.07.13 12:12:02 AM  czhower
{ Initial checkin
}
{
{   Rev 1.0    2003.05.19 2:54:00 PM  czhower
}
unit EZEngine;

interface

{
ELIZA -- an interactive parroting

Original Source: CREATIVE COMPUTING - MORRISTOWN, NEW JERSEY, late 1970's

Converted from Basic and some language called Inform to Delphi by
  Chad Z. Hower aka Kudzu in 2002 - email: chad at hower dot org
  Converted to objects, and implementation rewritten from scratch. Logic matched as best possible
  but BASIC code also had bugs in logic. Inform version also differed slightly, but probaly more
  accurate, but I am no Inform expert.

  Since that time I have made several custom modifications an improvements including the addition
  of personalities.

Note:
  Because of the conversion from older languages, this is not my best code.
  Slowly over time I am cleaning it up to make it more proper OO code. I am also
  expanding its capabilities beyond its original design.
}

uses
  Classes,
  EZPersonality;

type
  TEZEngine = class(TComponent)
  protected
    FConjugations: TStrings;
    FDone: Boolean;
    FLastMsg: string;
    FPersonality: TEZPersonality;
    //
    procedure InitConjugations;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure SetPersonality(const AName: string);
    function TalkTo(AMsg: string): string; overload;
    function TalkTo(AMsg: string; var VSound: string): string; overload;
    //
    property Done: Boolean read FDone;
    property Personality: TEZPersonality read FPersonality;
  end;

implementation

uses
  SysUtils, StrUtils;

{ TEZEngine }

constructor TEZEngine.Create(AOwner: TComponent);
begin
  inherited;
  FConjugations := TStringList.Create;
  InitConjugations;
end;

destructor TEZEngine.Destroy;
begin
  FreeAndNil(FPersonality);
  FreeAndNil(FConjugations);
  inherited;
end;

procedure TEZEngine.InitConjugations;
begin
  with FConjugations do begin
    Add('Are=am');
    Add('Were=was');
    Add('You=I');
    Add('Your=my');
    Add('I''ve=you''ve');
    Add('I''m=you''re');
    Add('Me=you');
  end;
end;

procedure TEZEngine.SetPersonality(const AName: string);
begin
  FreeAndNil(FPersonality);
  FPersonality := TEZPersonality.ConstructPersonality(AName);
end;

function TEZEngine.TalkTo(AMsg: string; var VSound: string): string;
var
  i, j: Integer;
  s: string;
  LConj: string;
  LFoundKeyword: string;
  LFoundKeywordIdx: Integer;
  LFoundKeywordPos: Integer;
  LKeyword: string;
  LWordIn: string;
  LWordOut: string;
begin
  VSound := '';
  if FPersonality = nil then begin
     raise Exception.Create('No personality has been specified.');
  end;
  Result := '';
  LConj := '';
  LFoundKeyword := '';
  LFoundKeywordIdx := FPersonality.Keywords.IndexOf('--NOKEYFOUND--');
  LFoundKeywordPos := 0;
  //
  AMsg := '  ' + Trim(AMsg) + '  ';
  AMsg := StringReplace(AMsg, '''', '', [rfReplaceAll]);
  // TODO: Respond to ones with ?
  // Replace with spaces so ' bug ' will match ' bug. ' etc.
  AMsg := StringReplace(AMsg, '?', ' ', [rfReplaceAll]);
  AMsg := StringReplace(AMsg, '!', ' ', [rfReplaceAll]);
  AMsg := StringReplace(AMsg, '.', ' ', [rfReplaceAll]);
  if AnsiSameText(AMsg, FLastMsg) then begin
    Result := 'Please don''t repeat yourself.';
  end else if AnsiContainsText(AMsg, 'SHUT ') then begin
    Result := 'How would you like it if I told you to shut up? I am sorry but we cannot continue'
     + ' like this. Good bye.';
    FDone := True;
  end else if Trim(AMsg) = '' then begin
    Result := 'I cannot help you if you do not talk to me.';
  end else begin
    FLastMsg := AMsg;
    // Find Keyword
    for i := 0 to FPersonality.Keywords.Count - 1 do begin
      LKeyword := FPersonality.Keywords[i];
      for j := 1 to Length(AMsg) - Length(LKeyword) + 1 do begin
        if AnsiSameText(Copy(AMsg, j, Length(LKeyword)), LKeyword) then begin
          LFoundKeywordIdx := i;
          LFoundKeyword := LKeyword;
          LFoundKeywordPos := j;
          Break;
        end;
        // Break out of second loop
        if LFoundKeyword <> '' then begin
          Break;
        end;
      end;
    end;
    // Take part of string and conjugate it using the list of strings to be swapped
    LConj := ' ' + RightStr(AMsg, Length(AMsg) - Length(LFoundKeyword) - LFoundKeywordPos + 1)
     + ' ';
    for i := 0 to FConjugations.Count - 1 do begin
      LWordIn := FConjugations.Names[i];
      LWordOut := FConjugations.Values[LWordIn] + ' ';
      LWordIn := LWordIn + ' ';
      LConj := StringReplace(LConj, LWordIn, LWordOut, [rfReplaceAll, rfIgnoreCase]);
    end;
    // Only one space
    if Copy(LConj, 1, 1) = ' ' then begin
      Delete(LConj, 1, 1);
    end;
    LConj := StringReplace(LConj, '!', '', [rfReplaceAll]);
    // Get reply
    s := TEZReply(FPersonality.Keywords.Objects[LFoundKeywordIdx]).NextText;
    VSound := TEZReply(FPersonality.Keywords.Objects[LFoundKeywordIdx]).Sound;
    if AnsiPos('*', s) = 0 then begin
      Result := s;
    end else if Trim(LConj) = '' then begin
      Result := 'You will have to elaborate more for me to help you.';
    end else begin
      Result := StringReplace(s, '*', LConj, [rfReplaceAll, rfIgnoreCase]);
    end;
  end;
end;

function TEZEngine.TalkTo(AMsg: string): string;
var
  LSound: string;
begin
  Result := TalkTo(AMsg, LSound);
end;

end.
