CIT 590/591 Assignment 6: Eliza
Fall 2012, David Matuszek

Purposes of this assignment:

General idea of the assignment:

The original ELIZA program was published by Joseph Weizenbaum in 1966. It simulated a Rogerian therapist; that is, most of what it said was merely repeating (with appropriate substitutions) what it was told. Here is a bit of sample dialogue (from Rich & Knight, Artificial Intelligence, 1983):

Person: Men are all alike.
ELIZA: In what way?
Person: They're always bugging us about something or other.
ELIZA: Can you think of a specific example?
Person: Well, my boyfriend made me come here.
ELIZA: Your boyfriend made you come here.
Person: He says I'm depressed much of the time.
ELIZA: I am sorry to hear you are depressed.
Person: It's true. I am unhappy.
ELIZA: Do you think that coming here will help you not to be unhappy?
Person: I need some help, that much seems certain.
ELIZA: What would it mean to you if you got some help?
Person: Perhaps I could learn to get along with my mother.
ELIZA: Tell me more about your family.

Many of Eliza's responses, you should notice, are simply restatements of what the user says. This is done by simply rearranging a few words and replacing a few pronouns. Other responses are simply keyed to specific words, such as "mother." When nothing works, the program might randomly choose one of a set of "all-purpose" phrases; for example, "Tell me more about that." Another good trick is to remember some previous topic, and revert to it. These tricks help to give the appearance that the program has some understanding of what the user says.

Your assignment is to write a simple ELIZA-like program that holds a plausible conversation with the user.

Concepts

ELIZA worked by applying pattern-matching rules. It looked for a rule whose left side matched the last sentence entered by the user, then used the right side of that rule to generate a response. Some sample rules might be:

I remember #1 => What made you think of #1?
#1 loves #2 => How does that make you feel about #1?
mother => Tell me more about your family.

The above are intended to show you the kind of rules you might have; it is not intended to say anything about how you should represent those rules in your program. While it is certainly possible to represent a rule by a string of the above form, for a small number of rules, it's easier just to write unique code for each kind of rule.

The conversation

Write an ELIZA-like program (in a class named Eliza) that interactively "converses" with the user. Your program should have the following features:

Try to get capitalization and punctuation correct, but don't worry too much about it.

Details

You should have (at least) the following classes, methods, and instance variables.:

class Eliza

def __init__(self):
Create an instance variable of Eliza named self.rules. Create a number of rules (one for each subclass of Rule) and put them into the self.rules variable.
def hold_conversation()
Prints out a welcoming message (of some sort) to the user. After that, it repeatedly reads in lines from the user (you can assume each thing the user "says" is on a single line), and computes and prints a response to it, usually by using rules to transform the input in some way. If no rules apply, a response is chosen randomly from a selection of responses.

Do the input/output in this method, but call get_response to do the actual pattern matching. When the user enters a line beginning with the word "goodbye," quit the Eliza program (do this by just returning from the hold_conversation method).
 
def get_response(user_input)
Given an input line from the user, applies the rules to produce and return Eliza's response.
Please put the following at the end of your program:
 if __name__ == '__main__':
    Eliza().hold_conversation()

class Rule

def apply(self, string)
Returns the given string. This method is never actually used; it's a "placeholder" method. Its whole purpose in life is to remind you that you need to override it by a method of the same name in each of its subclasses.
def can_apply(self, string)
Returns False. This method is never actually used; it's a "placeholder" method. Its whole purpose in life is to remind you that you need to override it by a method of the same name in each of its subclasses.
def is_final(self, string)
Returns False. Subclasses that do not override this method will automatically be non-final.
other methods
If there are methods that you want to use in more than one subclass of Rule, define them here and they will be inherited by all subclasses.

class Something_or_other_rule (Rule)

Write a separate class for each kind of rule that you can think of, and give each class an appropriate name. You should be able to come up with at least ten rules, including rules to do pronoun substitution and to provide random responses when nothing else applies. Each class should define the following:

def can_apply(self, string)
Returns true if applying this rule will make some changes in the string.
def is_final(self, string)
Returns True if, after this rule has been successfully applied, no other rules may be applied, and the response is final. In most rule classes you will probably not define this method, but instead just inherit the method from Rule, which returns False.
apply(self, string)
Do something with the given string to produce and return a response. Exactly what this method does depends on what kind of Rule class it is in. For example, if you write a class named Pronoun_substitution_rule, the apply_rule method in such a class probably should substitute pronouns in the given string to produce the resultant string.

Remember that string is derived from user input, and was probably a sentence or a question. However, when you are dealing with user input, you should not make any assumptions that you can avoid.
Possibly other local methods
You can inherit methods from Rule. You can override methods inherited from Rule. You can define additional methods to be used only within this class, or within a unit test for this specific class.

Comments

All classes and all methods should be fully commented with doc comments. The comment should consist of at least one sentence that describes what the method does, and it should describe the parameters and result.

Unit testing

Your goal in this assignment is to write an Eliza-like program that produces a reasonable-sounding "conversation." That means coming up with a set of rules that do a good job. Since I am not specifying exactly what those rules should be, I cannot provide unit tests for them.

You should unit test every method that you write, other than the methods defined in the Eliza class (Eliza is in charge of all the I/O). In particular, it should be easy to tell what each of your apply methods is supposed to do, by looking at the unit tests for it. Make each test reasonably readable and self-contained.

While it is common practice to have a test class for each application class, that isn't a requirement. In this assignment you will have a lot of subclasses of the Rule class, and there's nothing wrong in combining tests for those methods into one or a few test classes.

Due date:

Your program is due before Fall Break is next weekend, so we won't have lab or another assignment. Enjoy Fall Break!