AI in the 80s? How a Simple Animal Guessing Game Pioneered Machine Learning Before It Was Cool

Alexey Medvecky
9 min readDec 19, 2024

--

Introduction

Recently, I stumbled upon an old programming book on the shelf in the library of my childhood. Yellowed pages, the smell of dust, and lines printed in monochrome style. Among examples of seemingly long-outdated algorithms, I came across a game called “Guess the Animal.”

At first glance, it appeared completely primitive: the program asks questions, and the user answers “yes” or “no.” But the deeper I delved into the code, the more I was filled with a sense of wonder. This was a real self-learning program! It didn’t just attempt to guess the chosen animal but also learned from its mistakes, adding new questions and answers to its knowledge base. What’s more, it had the ability to save progress and load it during the next run.

In the 1980s, long before machine learning and the hype around artificial intelligence, programmers were already toying with the ideas of trainable algorithms. It wasn’t ChatGPT or AlphaGo but a simple game written in BASIC that used the same principles underlying modern AI systems.

Thus, I decided to recreate this algorithm in a modern programming language — C++ — and show how a simple children’s game from the past anticipated the era of machine learning.

Algorithm re-creation.

Main concepts

Decision Tree — How the Program “Thinks”
The foundation of the “Guess the Animal” game is a decision tree — a data structure that allows the program to ask questions step by step, clarifying details and narrowing down the range of possible answers. Each node in the tree can be either a question or an answer (an animal).
Let’s break down how this tree works in practice.

Tree Structure
Imagine a tree where each question is a fork. If the user answers “yes,” the program moves along the left branch, and if “no,” it moves along the right branch.
Questions are intermediate nodes that help the program progress further.
Answers (animals) are leaf nodes, where the program attempts to guess the chosen animal.
Here’s what it looks like:

                  Is it a mammal?
/ \
yes no
Whale? Penguin?
/ \ / \
(yes) (no) (yes) (no)

The program asks the question: “Is it a mammal?”
If the player answers “yes,” the program moves to the left and asks: “Is it a whale?”
If the answer is “yes,” the game has guessed correctly. If “no,” the program learns and adds a new node with a clarifying question.

How the Program “Learns”
The learning process is what makes the game unique. When the program makes a mistake, it asks the user to:

  • Name the chosen animal.
  • Specify a question that distinguishes the new animal from the previous one.
  • Indicate the correct answer (“yes” or “no”) to the new question for the new animal.

Example:
The program asks: “Is it a whale?”
The player answers “no” and says it’s a dolphin.
The program asks for clarification: “What question distinguishes a dolphin from a whale?”
The player answers: “Does it live in the ocean but isn’t as large?”

As a result, the program adds the new question and animals to the decision tree.
The updated structure looks like this:

               Is it a mammal?
/ \
yes no
Is it large? Penguin?
/ \
yes no
Whale Dolphin

The program “remembers” these changes, and during the next run, it will be able to guess the dolphin along with other animals.

Implementation in Code

In C++, the tree is represented as an array of nodes, where each node stores:

  • A question or an animal (as a string).
  • A flag isAnimal — to distinguish leaf nodes (answers) from questions.

Here is the node structure and the program’s logic:

struct TreeNode
{
std::string question; // Question or animal name
bool isAnimal; // True if the node represents an animal, false if it's a question
};

void askQuestion( int index )
{
if ( index >= MAX_NODES || tree[ index ].question.empty() )
{
std::cout << "Error: Invalid tree structure!" << std::endl;
return;
}

if ( tree[ index ].isAnimal )
{
// If it's a leaf node, make a guess
std::cout << "Is your animal: " << tree[ index ].question << "? (yes/no): ";
std::string answer;
std::cin >> answer;

if ( answer == "yes" )
{
std::cout << "Great! I guessed it!" << std::endl;
}
else
{
std::cout << "What was your animal?: ";
std::cin.ignore(); // Clear the input buffer
std::string newAnimal;
std::getline( std::cin, newAnimal );

std::cout << "What question differentiates " << newAnimal
<< " from " << tree[ index ].question << "?: ";
std::string newQuestion;
std::getline( std::cin, newQuestion );

std::cout << "For " << newAnimal << ", is the answer 'yes' or 'no' to this question?: ";
std::string newAnswer;
std::cin >> newAnswer;

// Update the tree with the new question and animals
int leftIdx = leftChild( index );
int rightIdx = rightChild( index );

if ( leftIdx < MAX_NODES && rightIdx < MAX_NODES )
{
tree[ leftIdx ] = { newAnimal, true }; // Add the new animal
tree[ rightIdx ] = { tree[ index ].question, true }; // Move the old animal
tree[ index ] = { newQuestion, false }; // Update the current node

// If the answer for the new animal is "no", swap the children
if ( newAnswer == "no" )
{
std::swap( tree[ leftIdx ], tree[ rightIdx ] );
}
}
else
{
std::cout << "Error: Tree capacity exceeded!" << std::endl;
}
}
}
else
{
// Ask the current question and continue traversal
std::cout << tree[ index ].question << " (yes/no): ";
std::string answer;
std::cin >> answer;

if ( answer == "yes" )
{
askQuestion( leftChild( index ) );
}
else
{
askQuestion( rightChild( index ) );
}
}
}

Example of Populating the Node Array:

Index | Question             | isAnimal   
----------------------------------------
1 | Is it a mammal? | false
2 | Whale | true
3 | Penguin | true

The program uses indices to navigate the tree. The root node always has index 1, and its child nodes are calculated using the formulas:

  • Left child: 2 * index
  • Right child: 2 * index + 1

Game Logic: Recursive Tree Traversal
The main function responsible for asking questions and learning is called askQuestion. It takes the index of the current node and works as follows:

  • If the node is an animal, the program attempts to guess.
  • If the node is a question, the program asks it and moves to the next node depending on the answer.

Let’s break it down with an example:
The program asks the question: “Is it a mammal?”

  • If the answer is “yes,” the program moves to the left branch and asks: “Is it a whale?”
  • If the answer is “no,” the program asks the user to add a new animal and a question.

In this way, the tree expands, and the program becomes “smarter.”

Saving and Loading Progress
To save and load the tree, the program uses a text file.

Saving the Tree:
The saveTreeToFile function saves non-empty tree nodes to a file as strings:

index|question|isAnimal  
void saveTreeToFile( const std::string & filename )
{
std::ofstream outFile( filename );
if ( !outFile )
{
std::cerr << "Error: Unable to open file for saving!" << std::endl;
return;
}

for ( int i = 1; i < MAX_NODES; i++ )
{
if ( !tree[ i ].question.empty() )
{
outFile << i << "|" << tree[ i ].question << "|" << tree[ i ].isAnimal << std::endl;
}
}

outFile.close();
std::cout << "Tree saved to " << filename << " successfully!" << std::endl;
}

Loading the Tree:
The loadTreeFromFile function restores the tree from the file

void loadTreeFromFile( const std::string & filename )
{
std::ifstream inFile( filename );
if ( !inFile )
{
std::cerr << "Error: Unable to open file for loading!" << std::endl;
return;
}

// Clear the tree
for ( int i = 1; i < MAX_NODES; ++i )
{
tree[ i ] = { "", false };
}

int index;
std::string question;
bool isAnimal;
std::string line;

while ( std::getline( inFile, line ) )
{
size_t pos1 = line.find( '|' );
size_t pos2 = line.rfind( '|' );

index = stoi( line.substr( 0, pos1 ) );
question = line.substr( pos1 + 1, pos2 - pos1 - 1 );
isAnimal = stoi( line.substr( pos2 + 1 ) );

tree[ index ] = { question, isAnimal };
}

inFile.close();
std::cout << "Tree loaded from " << filename << " successfully!" << std::endl;
}

Main Menu of the Game
The main function implements a user interface with the following options:

  • Play (p): Start guessing animals.
  • Save progress (s): Save the current tree to a file.
  • Load progress (l): Restore the tree from a file.
  • Quit (q): Exit the program.
int main()
{
// Initialize the tree with a single animal
tree[ 1 ] = { "Whale", true };

std::cout << "Think of an animal, and I will try to guess it." << std::endl;

while ( true )
{
std::cout << "Do you want to (p)lay, (s)ave, (l)oad, or (q)uit?: ";
char choice;
std::cin >> choice;

if ( choice == 'p' )
{
askQuestion( 1 ); // Start at the root node
}
else if (choice == 's')
{
saveTreeToFile( "tree_data.txt" );
}
else if ( choice == 'l' )
{
loadTreeFromFile( "tree_data.txt" );
}
else if ( choice == 'q' )
{
break;
}
else
{
std::cout << "Invalid choice! Please enter 'p', 's', 'l', or 'q'." << std::endl;
}
}

std::cout << "Thanks for playing!" << std::endl;

return EXIT_SUCCESS;
}

Implementation of the “Guess the Animal” Game in BASIC

The provided listing represents a classic implementation of the “Guess the Animal” game in BASIC — a programming language popular in the 1970s and 1980s. It is an amazing example of how, even with such a simple language, it is possible to build a self-learning algorithm with dynamic expansion of “knowledge” and the ability to save new questions.

10 PRINT TAB(32); "ANIMAL"
20 PRINT TAB(15); "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY"
30 PRINT: PRINT: PRINT
40 PRINT "PLAY 'GUESS THE ANIMAL'"
50 PRINT "THINK OF AN ANIMAL AND THE COMPUTER WILL TRY TO GUESS IT."
60 PRINT
70 DIM A$(200)
80 FOR I=0 TO 3
90 READ A$(I)
100 NEXT I
110 N=VAL(A$(0))

120 REM MAIN CONTROL SECTION
130 INPUT "ARE YOU THINKING OF AN ANIMAL"; A$
140 IF A$="LIST" THEN 600
150 IF LEFT$(A$,1)<>"Y" THEN 120
160 K=1
170 GOSUB 390
180 IF LEN(A$(K))=0 THEN 999
190 IF LEFT$(A$(K),2)="\Q" THEN 170
200 PRINT "IS IT A "; RIGHT$(A$(K),LEN(A$(K))-2);
210 INPUT A$
220 A$=LEFT$(A$,1)
230 IF A$="Y" THEN PRINT "WHY NOT TRY ANOTHER ANIMAL?": GOTO 120
240 INPUT "THE ANIMAL YOU WERE THINKING OF WAS A "; V$
250 PRINT "PLEASE TYPE IN A QUESTION THAT WOULD DISTINGUISH A"
260 PRINT V$; " FROM A "; RIGHT$(A$(K),LEN(A$(K))-2)
270 INPUT X$
280 PRINT "FOR A "; V$; " THE ANSWER WOULD BE ";
290 INPUT A$
300 A$=LEFT$(A$,1): IF A$<>"Y" AND A$<>"N" THEN 280
310 IF A$="Y" THEN B$="N"
320 IF A$="N" THEN B$="Y"
330 Z1=VAL(A$(0))
340 A$(0)=STR$(Z1+2)
350 A$(Z1)=A$(K)
360 A$(Z1+1)="\A"+V$
370 A$(K)="\Q"+X$+"\"+A$+STR$(Z1+1)+"\"+B$+STR$(Z1)+"\"
380 GOTO 120

390 REM SUBROUTINE TO PRINT QUESTIONS
400 Q$=A$(K)
410 FOR Z=3 TO LEN(Q$)
415 IF MID$(Q$,Z,1)<>"\" THEN PRINT MID$(Q$,Z,1);: NEXT Z
420 INPUT C$
430 C$=LEFT$(C$,1)
440 IF C$<>"Y" AND C$<>"N" THEN 410
450 T$="\"+C$
455 FOR X=3 TO LEN(Q$)-1
460 IF MID$(Q$,X,2)=T$ THEN 480
470 NEXT X
475 STOP
480 FOR Y=X+1 TO LEN(Q$)
490 IF MID$(Q$,Y,1)="\" THEN 510
500 NEXT Y
505 STOP
510 K=VAL(MID$(Q$,X+2,Y-X-2))
520 RETURN

530 DATA "4","\QDOES IT SWIM\Y2\N3\","\AFISH","\ABIRD"
600 PRINT: PRINT "ANIMALS I ALREADY KNOW ARE:"
605 X=0
610 FOR I=1 TO 200
620 IF LEFT$(A$(I),2)<>"\A" THEN 650
624 PRINT TAB(12*X);
630 FOR Z=3 TO LEN(A$(I))
640 IF MID$(A$(I),Z,1)<>"\" THEN PRINT MID$(A$(I),Z,1);:NEXT Z
645 X=X+1: IF X>5 THEN X=0: PRINT
650 NEXT I
660 PRINT
670 PRINT
680 GOTO 120
999 END

Analysis of the Implementation
The decision tree is represented as a linear array with encoded nodes and transitions.

Self-learning logic is implemented through:

  • Adding new questions and answers to the array.
  • Rewriting the current node with a new question and transitions.

Limitations:

  • Maximum number of nodes: 200.
  • The structure is stored only in memory (no disk saving).

Advantages:

  • A simple and elegant approach to learning based on feedback.
  • Clear structure and minimal logic for processing answers.

Conclusion

The implementation in BASIC is a historical example of effectively using limited resources. It demonstrates how a decision tree and a self-learning algorithm can be implemented on a simple language. However, it has limitations:

  • No ability to save progress.
  • String parsing complicates navigation and slows down program execution.

The implementation in C++ shows how modern tools make code more structured, readable, and efficient:

  • The use of data structures and indices simplifies tree navigation.
  • File input/output enables saving and restoring progress between sessions.

From an algorithmic complexity perspective, both implementations have the same asymptotic behavior for searching and learning. However, C++ is faster in constant time due to more efficient data handling.

Final Thoughts
Both versions of the “Guess the Animal” program are examples of how simple ideas, such as decision trees and self-learning, anticipated modern AI technologies. In the 1980s, without big data or neural networks, programmers were already creating algorithms capable of “learning” from mistakes and adapting to new information.

This reminds us that the key lies in the idea and algorithm, not the complexity of the tool.

--

--

Alexey Medvecky
Alexey Medvecky

Written by Alexey Medvecky

Embedded Software Engineer, Retro Computers enthusiast

Responses (4)