-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathanimals.tiny
127 lines (113 loc) · 3.34 KB
/
animals.tiny
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
########################################################################################
#
# 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.
#
########################################################################################
fn Node -> {
#
# Method that reads a "yes" or "no" (or something approximating) those and returns true
# if yes was entered.
#
promptYesNo: {prompt ->
while (true) {
match this.promptString(prompt)
| r/^ *y/ -> return true
| r/^ *n/ -> return false
| r/^ *q/ -> throw "All done"
}
}
#
# Method that writes a prompt and reads a string of input.
#
promptString: { prompt ->
print("$prompt ")
return readLine()
}
}
#
# The Animal 'class' which 'inherits' from Node
#
fn Animal name -> Node() + {
_name: name
Ask: {
# Hit a leaf, so see if we guessed it.
if (this.promptYesNo("Is it a ${this._name}?")) {
println("I won! Let's play again!")
return null
}
# Nope, so add a new animal and turn this node into a branch.
name = this.promptString("I lost! What was your animal?")
questionToAsk = this.promptString(
"What question would distinguish a ${this._name} from a $name?")
isYes = this.promptYesNo(
"Is the answer to the question 'yes' for a $name?")
println("I'll remember that. Let's play again!")
animal = Animal(name)
return Question(questionToAsk,
(if (isYes) {animal} else {this}),
(if (isYes) {this} else {animal}))
}
}
#
# The Question 'class' which also 'inherits' from Node
#
fn Question question ifYes ifNo -> Node() + {
_question : question
_ifYes : ifYes
_ifNo : ifNo
Ask: {
# Recurse into the branches.
if (this.promptYesNo(this._question)) {
result = this._ifYes.ask()
if (result != null) {
this._ifYes = result
}
}
else {
result = this._ifNo.ask()
if (result != null) {
this._ifNo = result
}
}
return null
}
}
#
# Object containing the game loop. Play until the user quits.
#
Game = {
play: {
try {
while (true) {
database.Ask()
}
}
catch {
# if it isn't the expected exception, then show the error.
if (it !~ r/all done/) {
error(it)
}
}
}
}
#
# Initialize the animal database.
#
database =
Question("Does it fly?",
Question("Does it havd a red breast?",
Animal("robin"),
Animal("stork")),
Question("Does it live in the water?",
Question("Is it a mammal?",
Animal('otter'),
Animal("frog")),
Animal("goat")));
game.play()