Newsgroups: rec.arts.int-fiction
Path: gmd.de!xlink.net!sol.ctr.columbia.edu!howland.reston.ans.net!europa.eng.gtefsd.com!news.umbc.edu!eff!news.kei.com!ub!acsu.buffalo.edu!goetz
From: goetz@cs.buffalo.edu (Phil Goetz)
Subject: Conversation, Eliza (Re: NPC interaction)
Message-ID: <Cn1nxw.5C3@acsu.buffalo.edu>
Sender: nntp@acsu.buffalo.edu
Nntp-Posting-Host: baten.cs.buffalo.edu
Organization: State University of New York at Buffalo/Comp Sci
References: <Cn0r1C.5rM@viewlogic.com>
Date: Tue, 22 Mar 1994 02:22:44 GMT
Lines: 206

In article <Cn0r1C.5rM@viewlogic.com>, Dave Allen <allen@viewlogic.com> wrote:
>Wes Modes (modes@admin.mport.com) wrote:
>
>I don't think you can do this with TADS.  I can't think of how, anyway.
>What I think you said was the parser should understand BILL, GO NORTH
>as a command, but BILL, GO TO HELL as text for an Eliza style keyword parser.
>What you want is to determine, in the preparser, whether the rest of the
>text will produce an error if parsed.
>
>What you could do instead is treat text in quotes as special.  Or maybe
>you could use a verb like SAY which takes up to the next period as a
>special string.  That would allow:
>
>BILL, GO NORTH  -- a command
>BILL,  "GO TO HELL" -- a string for Eliza
>"GO TO HELL" -- if you were already talking to Bill, or he's the only one there
>SAY GO TO HELL. BILL, GO NORTH. -- "go to hell" is a string, "go north" a
>   command.
>
>Would that be good enough?  Or would the quotes be distracting?  It's kind
>of like a special "conversation mode", like the directed dialog code which
>was mentioned here recently.

<<talking about _Inmate_ mode ON>>

Here's what I did in _Inmate_:  Everything in quotes
was parsed as one object, QUOTE, which was the only permissible object
of the verb SAY.  ASK was a synonym for SAY; TELL could be used via
the transformation

	TELL <person> QUOTE -> SAY QUOTE TO <person>

NOTE:	Transformations are great!  How come no one else uses them?
They let you do things like

	<transverb> <indirect obj> <obj> -> <transverb> <obj> TO <ind obj>

Anyway.  So you could

	Tell Dr. Sheffield "I'm tired."
	Ask Mark "Why is there air?"
	Say "Hi!" to Joe.

Your command would be parsed and acted on.  The SAY verb handler
would then pass the string in quotes to the parser, which would
make the character spoken to go ahead and execute the command if it
were such.  (I don't recall how I handled commands which the character
didn't want to obey.  Probably that was handled by the verb handler.
Or possibly I just had each verb flagged with a bit that would be on
if an NPC would obey it - an unsatisfactory solution, I agree.)

If the string didn't parse, it would pass it to the Eliza parser.
This was a little more than an Eliza parser, since it could invoke
actions (e.g. "Ask Mark 'May I please borrow your camera?'" would
cause him to either give you the camera or tell you why he wouldn't
give it to you).

>Could you, or some other kind soul, post that code?  I'd like to see how it
>works.  If you prefer, e-mail it to me.  I'll put it on the if-archive.

Here's my old-fashioned (functional) LISP code for Eliza.
This is not the Eliza parser mentioned above,
which was written in 6502 assembly.

(setq patterns
	'(
	 ((0 alike 1)	((In what way ?)))
	 ((how are you 0)	((I'm fine thank you.)))
	 ((0 you are 1)	((Would you prefer it if I weren't 1 ?)))
	 ((0 are you 1)	((Would you prefer it if I weren't 1 ?)))
	 ((0 i am 1)	((Why do you think you are 1 ?)))
	 ((0 are 1)	((What if they were not 1 ?)))
	 ((0 always 1)	((Can you think of a specific example ?)(When ?)(Always ?)))
	 ((0 what 1)	((Why do you ask ?)(Does that interest you ?)))
	 ((0 i want 1)	((What would it mean to you to get 1 ?)))
	 ((0 i 1)	((Why do you 1 ?)(Sometimes I 1 too.)))
	 ((0 you 1)	((I 1 ?)))
	 ((no 0)	((Try not to concentrate on the negative.)))
	 ((yes 0)	((You seem confident about that.)))
	 ((because 0)	((Is that the real reason ?)))
	 ((0)		((Really ?)(Go on.)(Interesting.)(0 ?)))
	)
)
(setq changelist
	'((i you)(me you)(you i)(my your)(your my)(mine yours)(yours mine)
	  (yourself myself)(myself yourself)
	  (are am)(am are)(was were)(were was)
)	 )

;Print a list without the end parenthesis
(defun printlist (l)
	(cond	((null l) NIL)
		(t	(princ (car l))(princ " ")
			(printlist (cdr l))
)	)	)

;(addalist '((HI))) returns (NIL (HI))
(defun addalist (biglist)
	(cond	((listp biglist) (cons nil biglist))
		(t (list nil))		;	(eq biglist T)
)	)

;(constofirstlist '((OF) NIL (DAWN TREADER)) '(VOYAGE)) returns ((VOYAGE OF) NIL (DAWN TREADER))
(defun constofirstlist (biglist elemnt)
	(cons (cons elemnt (car biglist)) (cdr biglist))
)

(defun match (input pattern)
    (let ((bindings NIL))
	(cond	((and (null input)(null pattern))
		 t)
		((null pattern) NIL)
		((equal (car input)(car pattern))	;Matched a word
		 (match (cdr input)(cdr pattern)))
		((numberp (car pattern))			;Variable
		 (cond	((setq bindings (match input (cdr pattern)))	;Match to NIL
			 (addalist bindings))
			((null input) NIL)
			((setq bindings (match (cdr input) pattern))	;Var absorbs a word
			 (constofirstlist bindings (car input)))
		))
		((null input) NIL)
	)
)   )

(defun bind (template bindings)
	(cond	((null template) nil)
		((numberp (car template))
		 (let	((replacement (nth (car template) bindings)))
			(append replacement (bind (cdr template) bindings))
		))
		(t
		 (cons (car template)(bind (cdr template) bindings))
)	)	)

;Take the list of bindings and change its point of view from that of the user
;to Eliza's.
;Ex:  (pov ((I AM)(FEELING WELL))) returns ((YOU ARE)(FEELING WELL))
;The top loop calls pov2 on every list in bindings.
(defun pov (bindings)
	(cond	((null bindings)	NIL)
		(t	(cons (pov2 (car bindings)) (pov (cdr bindings))))
)	)
;pov2 calls pov3 on every word in a list.
(defun pov2 (binding)
	(cond	((null binding)	nil)
		(t	(cons	(pov3 (car binding) changelist) (pov2 (cdr binding))))
)	)
;pov3 checks a word against every word in changelist
(defun pov3 (word clist)
	(cond	((null clist)	word)
		((equal word (caar clist))	(cadar clist))
		(t	(pov3 word (cdr clist)))
)	)

(defun rotate (l)
	(append (cdr l) (list (car l)))
)
;rotatepattern is used to change the global variable "patterns",
;rotating the replies in entry patresp.
;It does not modify "patterns" itself, but returns the list which
;patterns will then be setq to.
(defun rotatepattern (patresp patlist)
	(cond	((null patlist) NIL)
		((equal (car patlist) patresp)
		 (cons	(cons (car patresp) (list(rotate (cadr patresp))))
			(cdr patlist)
		))
		(t	(cons	(car patlist)
				(rotatepattern patresp (cdr patlist))
)	)	)	)

(defun reply (input)
	(let	((bindings nil))
		(dolist	(patresp patterns)
			(cond	((setq bindings (match input (car patresp)))
				 (setq patterns (rotatepattern patresp patterns))
				 (setq bindings (pov bindings))
				 (return (bind (caadr patresp) bindings))
)	)	)	)	)

(defun quit (input)
	(or	(equal input '(quit))
		(equal input '(exit))
		(equal input '(end))
		(equal input '(bye))
		(equal input '(goodbye))
		(equal input '(go away))
)	)

(defun eliza	()
	(format t "Hello, make yourself comfortable.~%")
	(format t "Just say whatever is on your mind.~%")
	(format t "Enter it in parenthesis (like the next line).~%")
	(format t "(But please don't use any punctuation.)~%")
	(loop
		(let	((input (read)))
			(cond	((quit input)
				 (print 'Goodbye.)
				 (return NIL))
				(t	(printlist (reply input))
					(terpri)
)	)	)	)	)


Phil goetz@cs.buffalo.edu
