############################################################################
#
#	File:     animal.icn
#
#	Subject:  Program to play "animal" guessing game
#
#	Author:   Robert J. Alexander
#
#	Date:     March 2, 1996
#
############################################################################
#
#   This file is in the public domain.
#
############################################################################
#  
#     This is the familiar ``animal game'' written in Icon.  The
#  program asks its human opponent a series of questions in an attempt
#  to guess what animal he or she is thinking of.  It is an ``expert
#  system'' that starts out with limited knowledge, knowing only one
#  question, but gets smarter as it plays and learns from its opponents.
#  At the conclusion of a session, the program asks permission to
#  remember for future sessions that which it learned.  The saved file
#  is an editable text file, so typos entered during the heat of battle
#  can be corrected.
#  
#     The game is not limited to guessing only animals.  By simply
#  modifying the first two lines of procedure "main" a program can be
#  created that will happily build a knowledge base in other categories.
#  For example, the lines:
#  
#	   GameObject := "president"
#	   Tree := Question("Has he ever been known as Bonzo",
#	      "Reagan","Lincoln")
#  
#  can be substituted, the program works reasonably well, and could even
#  pass as educational.  The knowledge files will automatically be kept
#  separate, too.
#  
#     Typing "list" at any yes/no prompt will show an inventory of
#  animals known, and there are some other commands too (see procedure
#  Confirm).
#  
############################################################################

global GameObject,Tree,Learn
record Question(question,yes,no)

#
#  Main procedure.
#
procedure main()
   GameObject := "animal"
   Tree := Question("Does it live in water","goldfish","canary")
   Get()	 # Recall prior knowledge
   Game()	 # Play a game
   return
end

#
#  Game() -- Conducts a game.
#
procedure Game()
   while Confirm("Are you thinking of ",Article(GameObject)," ",GameObject) do
	 Ask(Tree)
   write("Thanks for a great game.")
   if \Learn &
	 Confirm("Want to save knowledge learned this session") then Save()
   return
end

#
#  Confirm() -- Handles yes/no questions and answers.
#
procedure Confirm(q[])
   local answer,s
   static ok
   initial {
      ok := table()
      every ok["y" | "yes" | "yeah" | "uh huh"] := "yes"
      every ok["n" | "no"  | "nope" | "uh uh" ] := "no"
      }
   while /answer do {
      every writes(!q)
      write("?")
      case s := read() | exit(1) of {
	 #
	 #  Commands recognized at a yes/no prompt.
	 #
	 "save": Save()
	 "get": Get()
	 "list": List()
	 "dump": Output(Tree)
	 default: {
	    (answer := \ok[map(s,&ucase,&lcase)]) |
		     write("This is a \"yes\" or \"no\" question.")
	    }
	 }
      }
   return answer == "yes"
end

#
#  Ask() -- Navigates through the barrage of questions leading to a
#  guess.
#
procedure Ask(node)
   local guess,question
   case type(node) of {
      "string": {
	 if not Confirm("It must be ",Article(node)," ",node,", right") then {
	    Learn := "yes"
	    write("What were you thinking of?")
	    guess := read() | exit(1)
	    write("What question would distinguish ",Article(guess)," ",
			guess," from ",Article(node)," ",node,"?")
	    question := read() | exit(1)
	    if question[-1] == "?" then question[-1] := ""
	    question[1] := map(question[1],&lcase,&ucase)
	    if Confirm("For ",Article(guess)," ",guess,", what would the _
		  answer be") then return Question(question,guess,node)
	    else return Question(question,node,guess)
	    }
	 }
      "Question": {
	 if Confirm(node.question) then 
	       node.yes := Ask(node.yes)
	 else
	       node.no := Ask(node.no)
	 }
      }
end

#
#  Article() -- Come up with the appropriate indefinite article.
#
procedure Article(word)
   return if any('aeiouAEIOU',word) then "an" else "a"
end

#
#  Save() -- Store our acquired knowledge in a disk file name
#  based on the GameObject.
#
procedure Save()
   local f
   f := open(GameObject || "s","w")
   Output(Tree,f)
   close(f)
   return
end

#
#  Output() -- Recursive procedure used to output the knowledge tree.
#
procedure Output(node,f,sense)
   static indent
   initial indent := 0
   /f := &output
   /sense := " "
   case type(node) of {
      "string":  write(f,repl(" ",indent),sense,"A: ",node)
      "Question": {
	 write(f,repl(" ",indent),sense,"Q: ", node.question)
	 indent +:= 1
	 Output(node.yes,f,"y")
	 Output(node.no,f,"n")
	 indent -:= 1
	 }
      }
   return
end

#
#  Get() -- Read in a knowledge base from a disk file.
#
procedure Get()
   local f
   f := open(GameObject || "s","r") | fail
   Tree := Input(f)
   close(f)
   return
end

#
#  Input() -- Recursive procedure used to input the knowledge tree.
#
procedure Input(f)
   local nodetype,s
   read(f) ? (tab(upto(~' \t')) & =("y" | "n" | "") &
	 nodetype := move(1) & move(2) & s := tab(0))
   return if nodetype == "Q" then Question(s,Input(f),Input(f)) else s
end

#
#  List() -- Lists the objects in the knowledge base.
#
procedure List()
   local lst,line,item
   lst := Show(Tree,[])
   line := ""
   every item := !sort(lst) do {
      if *line + *item > 78 then {
	 write(trim(line))
	 line := ""
	 }
      line ||:= item || ", "
      }
   write(line[1:-2])
   return
end

#
#  Show() -- Recursive procedure used to navigate the knowledge tree.
#
procedure Show(node,lst)
   if type(node) == "Question" then {
      lst := Show(node.yes,lst)
      lst := Show(node.no,lst)
      }
   else put(lst,node)
   return lst
end
