Recreating the Twenty Questions Game in Java

Originated in the United States and played widely in the 19th century, Twenty Questions is a spoken parlor game which encourages deductive reasoning and creativity. In that tutorial, you are going to learn how to recreate the Twenty Questions Game in Java.

You can also discover this tutorial in video on YouTube:

Rules

In the Twenty Questions game, one player is chosen to be the answerer. That person chooses an object or an animal but doesn’t reveal this to the others. The other player is the questioner. He asks questions which can be answered with a simple “Yes” or “No”. Sample questions could be : “Can it fly?” or “Does it sometimes live in water?”. If the questioner guesses the correct answer before asking for 20 Questions, he wins. Otherwise, if he asks 20 questions without a correct guess, the answerer has stumped the questioner and he won.

Recently, the Akinator game took the original concept of the Twenty Questions game and adapted it to Android and iOS smartphones with great success. Even better, the version of the Akinator game is a more complex one since the answer “Maybe” is allowed in addition to the simple “Yes” and “No”.

For our implementation, we will be satisfied with the original game with the simple answers “Yes” and “No” and we will not limit the number of questions to 20 to give more chances to the computer, which will play the role of the questioner, to find the object that the player will think of. For making our Twenty Questions Game more attractive, we will use a Swing UI.

Prerequisites

Our game will be based on a tree containing questions with simple “Yes” and “No” answers. We will therefore be in the presence of a binary tree. Before starting the implementation, you can find out more about trees, their implementation and their traversal algorithms with examples in Java in the following article:

Representing the Tree

Our Tree will have nodes represented by the TreeNode class. Each node can be a Question or an Answer. We will use a Type enum for representing that. Each node have a String data and can have two children : a yes and a no TreeNode instances. Obviously, if the TreeNode is an answer, he will have no children.

The decision tree containing the questions will be loaded from a raw file. Each line representing a question will start with a “Q:” flag and each line representing an answer will start with a “A:” flag. So, we need to add two constants in our TreeNode and when we will set data of the TreeNode object, we will determine the type of the TreeNode according this flag.

It gives us the following code for the TreeNode class:

package com.ssaurel.twentyquestions;
// We will use a Binary Tree to represent the questions
// for guessing what user thinks about
// So we need to represent tree nodes
public class TreeNode {
enum Type {
ANSWER, QUESTION // nodes are answer or question
}
public static final String QUESTION_FLAG = "Q:";
public static final String ANSWER_FLAG = "A:";
public String data;
public Type type;
// if tree node is a question, he has two children (one for yes, one for no)
public TreeNode yes;
public TreeNode no;
public TreeNode() {
}
public TreeNode(String data, Type type) {
this.data = data;
this.type = type;
}
public boolean isQuestion() {
return Type.QUESTION.equals(type);
}
public void setData(String data) {
type = Type.QUESTION;
if (data.startsWith(ANSWER_FLAG)) {
type = Type.ANSWER;
}
this.data = data.substring(2); // we remove question or answer flag
}
public void addYes(TreeNode yes) {
this.yes = yes;
}
public void addNo(TreeNode no) {
this.no = no;
}
}

Loading the Decision Tree data

Most important part of the Twenty Questions Game is probably to have a great Decision Tree of questions and answers. It will make the difference and it will let our computer to find fastly the object thought by the user. To make our program more flexible and reusable, the data will be loaded from a raw text file. Like that, it would be easy to extend our Twenty Questions Game to let users load new Decision Tree data.

The data are stored in the raw file following Breadth First Search strategy. It is important to know that information for the loading method we will write in our Tree class. The Tree class has a root property of TreeNode type. We define a loadTree method taking in parameter the filename of a raw file containing Decision Tree data.

We open the file and we call a buildTree method with the root in parameter and the BufferedReader instance used to read the file line by line. When we read a line, we make the following actions :

  • Set data in the current node
  • If current node is a question :

– We create a yes TreeNode and a no TreeNode.
– We set these nodes as children of the currentNode.
– We call recursively buildTree method for building the yesNode subTree from the file.
– We call recursively buildTree method for building the noNode subTree from the file

It gives us the following implementation for the Tree class:

package com.ssaurel.twentyquestions;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
// we represent the tree for Twenty Question game
public class Tree {
public TreeNode root; // root of our tree
public Tree() {
root = new TreeNode();
}
// we load data from our file in a Tree instance
public void loadTree(String filename) {
File file = new File(filename);
FileReader fileReader = null;
BufferedReader buf = null;
try {
fileReader = new FileReader(file);
buf = new BufferedReader(fileReader);
buildTree(root, buf);
} catch (Exception e) {
System.out.println("Error during tree building : " + e.getMessage());
} finally {
try {
if (fileReader != null) {
fileReader.close();
}
if (buf != null) {
buf.close();
}
} catch (Exception e) {
}
}
}
// we build the three from a buffered reader with a breadth first search strategy
private void buildTree(TreeNode currentNode, BufferedReader buf) throws Exception {
String line = buf.readLine();
if (line != null) {
currentNode.setData(line);
if (currentNode.isQuestion()) {
TreeNode yesNode = new TreeNode();
TreeNode noNode = new TreeNode();
currentNode.yes = yesNode;
currentNode.no = noNode;
buildTree(yesNode, buf);
buildTree(noNode, buf);
}
}
}
}
view raw Tree.java hosted with ❤ by GitHub

Note that our animals_questions.txt data file for the Decision Tree has the following form :

Q:Is it white?
Q:Is it a male?
Q:Is it yellow?
Q:Is it related to the cat family?
Q:Can it be all animals?
Q:Are you going to click yes for the last question?
Q:Are you going to answer “no” for this question?
Q:Can it do everything?
A:God
A:yes
A:yes
A:nothing
A:Bengal tiger
A:rabbit

Creating the User Interface

Now, it’s time to create the User Interface of our Twenty Questions Game. Our UI will have a text area for displaying questions to the user and three buttons :

  • Yes button for letting user to click yes and progressing in our Decision Tree
  • Start button for starting a new game
  • No button for letting user to click no and progressing in our Decision Tree

It will look like this:

You can note that our Decision Tree data are centered on animals. So, we ask to the user to think only to an animal.

We use the Swing API for building this User Interface. The text area is a JTextPane object and we use a BorderLayout instance to dispose the object in the content pane of the JFrame. The three buttons are represented via the JButton class.

It gives us the following buildUI method:

private void buildUI() {
// we build the UI
JFrame frame = new JFrame("Twenty Questions on SSaurel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container contentPane = frame.getContentPane();
// add buttons
JPanel buttonsPanel = new JPanel();
yesButton = new JButton("Yes");
yesButton.addActionListener(btnsListener);
buttonsPanel.add(yesButton, BorderLayout.LINE_START);
restartButton = new JButton("Start");
restartButton.addActionListener(btnsListener);
buttonsPanel.add(restartButton, BorderLayout.LINE_START);
noButton = new JButton("No");
noButton.addActionListener(btnsListener);
buttonsPanel.add(noButton, BorderLayout.LINE_END);
contentPane.add(buttonsPanel, BorderLayout.PAGE_END);
// add text area
textPane = new JTextPane();
textPane.setEditable(false);
updateText("Think to an animal, then click on Start");
// we define some style for the text pane
SimpleAttributeSet bSet = new SimpleAttributeSet();
StyleConstants.setAlignment(bSet, StyleConstants.ALIGN_CENTER);
StyleConstants.setFontSize(bSet, 22);
StyledDocument doc = textPane.getStyledDocument();
doc.setParagraphAttributes(0, doc.getLength(), bSet, false);
contentPane.add(textPane, BorderLayout.CENTER);
frame.setMinimumSize(new Dimension(500, 250));
// we center the JFrame
Dimension objDimension = Toolkit.getDefaultToolkit().getScreenSize();
int coordX = (objDimension.width - frame.getWidth()) / 2;
int coordY = (objDimension.height - frame.getHeight()) / 2;
frame.setLocation(coordX, coordY);
// we display the window
frame.pack();
frame.setVisible(true);
}

Interaction with the player

So, we have a Decision Tree implementation, some data and a User Interface. Now, we need to implement interaction with the player during the Twenty Questions game. We add an ActionListener instance to the three buttons for that. We define three methods :

  • yes method to manage behavior of our Twenty Questions Game when the player clicks on Yes for a question
  • no method to manage behavior of our Twenty Questions Game when the player clicks on No for a question
  • start method to manage the starting of the game

We assemble our game in a TwentyQuestions class. We define a currentNode property to keep a pointer of our position in the Decision Tree. At the beginning of the Twenty Questions game, currentNode points to the root of the Decision Tree. This step is implemented in the restart method.

When the yes method is called, we know that the user has clicked on yes for the current question or answer displayed. So, we need to follow the yes path of our current node. If the new current node is a question, we display the data contained within it on the screen. Otherwise, we display a message to the user if he thought to the answer present in the current node. If the new current node was null, we know that we found the good answer and we can display a “I found :)” message to the user.

The logic is the same for the no method. The difference is when the current node obtained by following the no path of the current node is null, we need to display a “I lose :(“ message to the user.

Complete code of the Twenty Questions Game

It gives us the following complete code for the TwentyQuestions class:

package com.ssaurel.twentyquestions;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextPane;
import javax.swing.SwingUtilities;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
// Main class for our Twenty Questions game with a Swing UI
public class TwentyQuestions {
private Tree tree;
private TreeNode currentNode;
private JButton yesButton, noButton, restartButton;
private JTextPane textPane; // for displaying the question
private boolean started = false;
// btns listener
private ActionListener btnsListener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Object source = e.getSource();
if (source == yesButton)
yes();
else if (source == noButton)
no();
else if (source == restartButton)
restart();
}
};
public static void main(String[] args) {
TwentyQuestions twentyQuestions = new TwentyQuestions();
twentyQuestions.tree = new Tree();
twentyQuestions.tree.loadTree("/Users/ssaurel/eworkspace/TwentyQuestions/animals_questions.txt");
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
twentyQuestions.buildUI();
}
});
}
private void buildUI() {
// we build the UI
JFrame frame = new JFrame("Twenty Questions on SSaurel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container contentPane = frame.getContentPane();
// add buttons
JPanel buttonsPanel = new JPanel();
yesButton = new JButton("Yes");
yesButton.addActionListener(btnsListener);
buttonsPanel.add(yesButton, BorderLayout.LINE_START);
restartButton = new JButton("Start");
restartButton.addActionListener(btnsListener);
buttonsPanel.add(restartButton, BorderLayout.LINE_START);
noButton = new JButton("No");
noButton.addActionListener(btnsListener);
buttonsPanel.add(noButton, BorderLayout.LINE_END);
contentPane.add(buttonsPanel, BorderLayout.PAGE_END);
// add text area
textPane = new JTextPane();
textPane.setEditable(false);
updateText("Think to an animal, then click on Start");
// we define some style for the text pane
SimpleAttributeSet bSet = new SimpleAttributeSet();
StyleConstants.setAlignment(bSet, StyleConstants.ALIGN_CENTER);
StyleConstants.setFontSize(bSet, 22);
StyledDocument doc = textPane.getStyledDocument();
doc.setParagraphAttributes(0, doc.getLength(), bSet, false);
contentPane.add(textPane, BorderLayout.CENTER);
frame.setMinimumSize(new Dimension(500, 250));
// we center the JFrame
Dimension objDimension = Toolkit.getDefaultToolkit().getScreenSize();
int coordX = (objDimension.width - frame.getWidth()) / 2;
int coordY = (objDimension.height - frame.getHeight()) / 2;
frame.setLocation(coordX, coordY);
// we display the window
frame.pack();
frame.setVisible(true);
}
// we need to write code for callbacks methods yes, no and restart
private void yes() {
// we navigate in the tree by moving the current node on the yes branch
if (started && currentNode != null) {
currentNode = currentNode.yes;
if (currentNode != null) {
if (currentNode.isQuestion()) {
updateText(currentNode.data);
} else {
updateText("Would you think to " + currentNode.data + "?");
}
} else {
updateText("I found :)");
}
}
}
private void no() {
// we navigate in the tree by moving the current node on the no branch
if (started && currentNode != null) {
currentNode = currentNode.no;
if (currentNode != null) {
if (currentNode.isQuestion()) {
updateText(currentNode.data);
} else {
updateText("Would you think to " + currentNode.data + "?");
}
} else {
updateText("I lose :(");
}
}
}
private void restart() {
if (started) {
started = false;
updateText("Think to an animal, then click on Start");
} else {
started = true;
updateText("Think to an animal, then click on Start");
currentNode = tree.root;
updateText(currentNode.data);
}
}
private void updateText(String txt) {
textPane.setText("\n" + txt);
}
}

We thought to an animal. An OSTRIGE for example :).

We click on Start and we answer to the question of the computer with “Yes” or “No”. At the end, the computer finds our animal and asks us the following question:

We click on “Yes”, and the we have the following display:

That’s all for that tutorial.

To discover more tutorials on Java and Android development, you can visit the SSaurel’s Channel on YouTube:

If you want to discover some books to learn Java programming, I advice you to read the following article with my selection of the Top 6 Best Books for Java programming:

Leave a Reply

Your email address will not be published. Required fields are marked *