import "io" for Stdin

// Implements the classic "Animals" guessing game. The user thinks of an animal.
// The program asks a series of yes/no questions to try to guess the animal
// they are thinking of. If the program fails, it asks the user for a new
// question and adds the animal to its knowledge base.
//
// Internally, the program's brain is stored as a binary tree. Leaf nodes are
// animals. Internal nodes are yes/no questions that choose which branch to
// explore.

class Node {
  // Reads a "yes" or "no" (or something approximating) those and returns true
  // if yes was entered.
  promptYesNo(prompt) {
    while (true) {
      var line = promptString(prompt)

      if (line.startsWith("y") || line.startsWith("Y")) return true
      if (line.startsWith("n") || line.startsWith("N")) return false

      // Quit.
      if (line.startsWith("q") || line.startsWith("Q")) Fiber.yield()
    }
  }

  // Writes a prompt and reads a string of input.
  promptString(prompt) {
    System.write("%(prompt) ")
    return Stdin.readLine()
  }
}

class Animal is Node {
  construct new(name) {
    _name = name
  }

  ask() {
    // Hit a leaf, so see if we guessed it.
    if (promptYesNo("Is it a %(_name)?")) {
      System.print("I won! Let's play again!")
      return null
    }

    // Nope, so add a new animal and turn this node into a branch.
    var name = promptString("I lost! What was your animal?")
    var question = promptString(
        "What question would distinguish a %(_name) from a %(name)?")
    var isYes = promptYesNo(
        "Is the answer to the question 'yes' for a %(name)?")
    System.print("I'll remember that. Let's play again!")

    var animal = Animal.new(name)
    return Question.new(question, isYes ? animal : this, isYes ? this : animal)
  }
}

class Question is Node {
  construct new(question, ifYes, ifNo) {
    _question = question
    _ifYes = ifYes
    _ifNo = ifNo
  }

  ask() {
    // Recurse into the branches.
    if (promptYesNo(_question)) {
      var result = _ifYes.ask()
      if (result != null) _ifYes = result
    } else {
      var result = _ifNo.ask()
      if (result != null) _ifNo = result
    }

    return null
  }
}

var root = Question.new("Does it live in the water?",
    Animal.new("frog"), Animal.new("goat"))

// Play games until the user quits.
Fiber.new {
  while (true) root.ask()
}.call()
