Lost In The Enchanted Forest
Time to read: 11 minutes
Lost in the Enchanted Forest
There existed an enchanted forest, veiled in mystery and wonder, in a realm where magic flourished. Enchanting streams gleamed in this woodland, where towering trees murmured secrets to the breeze. The charm of the woodland drew you — a curious individual with an unquenchable hunger for adventure — into its sacred chambers. Now, you need to find your way out.
Come along, and let us build an adventurous game together with Twilio’s Programmable Voice API. The game helps players navigate the forest by answering tricky questions and earning points. Playing this game could be your or anyone else’s only chance to make it out.
How the game works
The player is given a brief overview of the forest and their situation at the start of the game. The objective for the player is to investigate the area around them and work towards locating an escape route.
As they make their way through the forest's several enchanted areas, players come across a variety of obstacles, riddles, and people. The player has a variety of options and routes to choose from in each area. To make decisions, players can utilize voice commands such as "Go left", "Inspect the tree", or "Talk to the gnome". The game engine develops the story and has an impact on how the story finishes by deciphering these instructions and responding accordingly.
The game has a branching narrative wherein the player's choices impact the plot's direction and conclusion. The player may engage in a wide range of interactions, overcome a variety of obstacles, and reach a variety of decisions depending on their decisions.
As players go through the game, they will encounter interactive challenges and puzzles that will put their problem-solving and creative thinking to the test. Voice instructions are used to solve puzzles, interact with objects, and progress through the game.
Depending on the actions the player takes along the route, the game offers several different results. Each conclusion provides a unique means of bringing the story to a close and honors the decisions and research done by the players.
Prerequisites
Make sure you have the following before we start:
- JDK (Java Development Kit) 8 or later
- An IDE or text editor such as Eclipse, Visual Studio Code, or IntelliJ IDEA Community Edition
- Apache Maven
- A Twilio account (free or paid) with a phone number capable of sending SMS and making voice calls. Create a free account if you don't already have one
- Postman or another API testing tool, like Katalon and Newman
- A basic understanding of databases and relations, Java programming, and the Spring Boot framework would be helpful
Create a Spring Boot application
Establishing the project structure, defining the primary application class, and specifying dependencies are all necessary steps in creating a new Spring Boot application. This part of the tutorial will help you do that.
We'll use Spring Initializer to create a new Spring Boot project. First, visit Spring Initializr, an online application that creates Spring Boot projects. There, set the required dependencies, group, artifact, and other properties of the project, as shown in the screenshot below. Then, to get the produced project zip file, click Generate.
After that, unzip the generated zip archive where you normally create your Java projects, open your IDE (IntelliJ IDEA in this tutorial), and load the project.
Add the required dependencies
The dependencies
section of your project dependency file, pom.xml, should look like this:
You need to add four dependencies to the dependencies list above; these are:
- Twilio Java Helper Library to simplify interacting with Twilio Programmable Voice
- Project Lombok to avoid writing getters and setters
- Jakarta Persistence to be able to manage relational data in your project
- The JSON In Java dependency to work with JSON responses sent by Twilio
- Spring Boot Starter Web, which is a starter for building web applications using Spring MVC
To do that, in the dependencies
tag in your pom.xmlfile, add the following dependencies:
Then, click the Maven symbol that will appear, to download the dependencies into your project; which you can see in the screenshot below.
Configure your project properties
Configuration of your project’s properties happens in the src/main/resources/application.properties file.
Rename the file to src/main/resources/application.yml. This is because YAML is a convenient format for specifying hierarchical configuration data. For example, it does not have repeated prefixes, making it easier to understand than its property file alternative.
Like the name implies, the file can be used to set all the properties (like port, database credentials, third-party URLs, keys, and secrets) for your application.
At this time, we will not be using the file in its current state. In the com.example.enchanter
package, create a new class named AppCredential
. Then, paste the code below into the class.
The AppCredential
class is a Spring configuration class (denoted by @Configuration
) which holds the credentials and configuration settings needed for your application, particularly for interacting with Twilio.
Let's break down the fields and their purposes:
- accountSid: This field stores your Twilio Account SID. The Account SID is a unique identifier assigned to your Twilio account, and is required to authenticate requests made to Twilio's APIs. You can find your Account SID in the Account Info section of the Twilio Console.
- authToken: This field stores your Twilio Auth Token. The Auth Token is a secret token used alongside your Account SID to authenticate requests to Twilio's APIs. It provides access to your Twilio account and should be kept confidential. You can find your Auth Token in the Twilio Console near the Account SID.
- fromNumber: This field stores the Twilio phone number (or sender ID) from which your application sends messages or makes calls. This number must be purchased or provisioned through Twilio, and can be used to send SMS messages or make voice calls. You can find the phone number in the Twilio console in the "Phone Numbers" section.
The class is also annotated with @ConfigurationProperties("twilio")
which binds the properties with Twilio
from the configuration file which, in this case, is applications.yml file to the field in this class:
@Slf4j
annotation is from the Lombok library and generates a logger field for logging purposes.@Setter
and@Getter
are Lombok annotations that generate the setter and getter methods for all the fields in the class.Twilio.init(accountSid, authToken)
initializes the Twilio client with the provided account SID and authentication token. This is required for making API calls to Twilio.@PostConstruct
annotation indicates that theinit()
method will be executed after the bean's properties have been set. It's a lifecycle callback used to perform any initialization work.
Retrieve your Twilio credentials
To retrieve your Twilio Account SID, Auth Token, and phone number, login to your Twilio Console dashboard, where you can find the details in the Account Info section.
It is insecure to directly paste your Twilio (or any other credentials) directly into your codebase. So, we will place the credentials in the application's properties file, along with other sensitive data like your database connection values.
Open src/main/resources/application.yml and paste the following code in it:
Then, replace the three placeholders with the respective values from your Twilio account; from_number
is your Twilio phone number. Now, we can reference these values inside the code.
Create a TwilioCallService Class
The TwilioCallService
class is the class which we will be using to make the outbound calls from our application to the player’s phone number. In the com.example.enchanter
package, create a class named TwilioCallService
and paste the code below into it:
The makeCall()
method takes a parameter named toNumber
which represents the phone number of the recipient. The line Twilio.init(appCredential.getAccountSid(), appCredential.getAuthToken());
initializes the Twilio client with the Account SID and Auth Token retrieved from the AppCredential
object. This step is essential to authenticate API requests to Twilio. Then, it supplies the application's publicly accessible URL so that Twilio can make requests to your service.
Now, the block that creates the actual call:
Call.creator
: This is a static method from the Twilio SDK'sCall
class used to create a new phone callnew com.twilio.type.PhoneNumber(toNumber)
: Creates aPhoneNumber
object for the destination number (toNumber
)new com.twilio.type.PhoneNumber(appCredential.getFromNumber())
: Creates aPhoneNumber
object for the Twilio number (the "from" number), retrieved from theAppCredential
objectURI.create(NGROK_URL)
: Converts the NGROK URL string to aURI
object, used as the callback URL for Twilio's webhook.create()
: This method sends the request to Twilio to create the call. It initiates the phone call with the specified parameters.
Create the Message class
Next, create a class named Message.java in the com.example.enchanter
package, and paste the code snippet below into the file:
This class holds the messages to be passed or spoken to the player, depending on the stage of the game, and the score earned on a move.
Create a Controller class
In the com.example.enchanter
package, create a file named ApplicationController.java, and then paste the code below into the file:
The:
@RestController
annotation tells Spring that this class is a RESTful web service controller. It combines@Controller
and@ResponseBody
, eliminating the need to annotate each method with@ResponseBody
.@RequiredArgsConstructor
annotation is a Lombok feature that generates a constructor with parameters for all final fields. It tells Spring to inject an instance ofTwilioService
into this class, which handles the Twilio-related operations.newTwilioService
field is injected by Spring, due to the@RequiredArgsConstructor
annotation. This field is used to delegate the Twilio logic to theTwilioService
class.@PostMapping(value = "/twiml", produces = "application/xml")
annotation maps HTTP POST requests to the "/twiml" endpoint. Theproduces
attribute specifies that the method returns XML data.getTwiml()
method handles the GET request to "/twiml". It calls thegetTwimlResponse()
method of the newTwilioService
, which generates and returns a TwiML (Twilio Markup Language) response containing the instructions for the Twilio call.@PostMapping(value = "/handle-input", produces = "application/xml")
annotation maps HTTP POST requests to the "/handle-input" endpoint. Theproduces
attribute specifies that the method returns XML data.handleInput(HttpServletRequest request)
method handles the POST request to "/handle-input". It expects the request to contain parameters sent by Twilio after processing the call. This method delegates the processing tonewTwilioService.handleInput(request)
, which extracts the user's input from the request, processes it, and generates an appropriate TwiML response.
Create the TwilioService class
In the com.example.enchanter
package, create a file named TwilioService.java, and paste the code snippet below into it:
The score
field is initialized to zero and keeps track of the player's score throughout the game, while the gameActive
field, initialized to true
, indicates whether the game is still active or not.
The getTwimlResponse() method
This method generates a TwiML response, which Twilio uses to interact with the user via a phone call. The <Gather input="speech" timeout="10" action="/handle-input" language="en-GB">
uses the TwiML Gather verb to wait for the user's speech input for up to 10 seconds, and then sends it to the /handle-input
endpoint. Then, the <Say>Please say a direction to move in. North, South, East or West.</Say>
line recites a message, prompting the user for input.
The handleInput() method
The handleInput()
method processes the user's speech input from the HTTP request by retrieving the SpeechResult
parameter, and updates the game state based on this input using the updateGameState()
method.
It checks if the total score from the game update is equal to or greater than 20. If so, it returns a TwiML response congratulating the user on completing the game with their final score and ends the call.
If the score is less than 20, it returns a TwiML response that informs the user of their current response and score, and then uses the Gather verb to prompt the user to provide another direction to move in, with a 5-second timeout for the input. If no input is received within this time, it plays a message saying "Goodbye" and ends the call.
The updateGameState() method
The updateGameState()
method updates the game's score and state based on the user's input. The generateScore(speechResult)
generates a score for the given input, score += moveScore
updates the total score of the player.
While this.gameActive = shouldContinueGame(score)
determines if the game should continue based on the updated score. Finally, a map with the updated game state, including the total score, the user's input, and a remark based on the move score, is returned.
The generateScore() method
The generateScore()
method generates a random score based on the user's input and returns a random integer between zero and nine.
The shouldContinueGame() method
The shouldContinueGame()
method determines whether the game should continue based on the current score. The game continues as long as the score is less than 50.
The moveRemark() method
The moveRemark()
method provides a remark based on the move score. If movePoint
is less than or equal to two, it returns a "fair move" remark for low scores. Otherwise, it returns a "good move" remark for higher scores.
Create the main game class
Now, to the main class that controls the flow and instructions of the game. Update the EnchanterApplication class with the code below:
The run(String... args)
method prompts the user for their name and phone number, introduces the game, then makes a call to the provided phone number using newTwilioCallService()
.
Test the game
The first thing to do is to expose the application publicly on the internet, with ngrok, by running the command below:
Ngrok is a useful tool for exposing a local development server to the internet. It is especially useful for testing webhooks, APIs, and other services that require requests from external sources, such as third-party integrations or mobile apps, without having to deploy your code to a public server.
Copy the URL next to Forwarding, which is the publicly (exposed) version of http://localhost:8080. Then, open the Twilio Console, click on PhoneNumber > Manage > Active Numbers, and click on the phone number you’re using for this tutorial.
After that, under Voice Configuration:
- Set Configure with the "Webhook, TwiML, Bin, Function, Studio Flow, Proxy Service"
- Set the value of the URL field next to "A call comes in" to the Forwarding URL that you copied from ngrok's terminal output, followed by "/twiml"
- Next to the URL field set the HTTP field to "HTTP POST"
- Click Save configuration
Next, in the TwilioCallService class, replace the <YOUR_WEBHOOK_URL>
placeholder with your ngrok URL, appended with the /twiml
path from our ApplicationController class.
Now, to test the game, we need to run it. There are different ways to run a Java project, depending on the IDE you are using. Please read up on how to run a project with your IDE if it is different from IntelliJ IDEA.
In IntelliJ IDEA, you can run the game by having EnchanterApplication
as the active tab, setting the Run/debug configuration dropdown to Current File (it's located on the top right hand side of the IDE interface), then clicking the green play button.
Now that the application is running, you can supply your name and phone number and start playing the game.
This tutorial was written with test credentials, so we are using a registered number to receive calls from Twilio. When the call comes in, take it. You will hear the first instruction asking you to make the first move. All you need to do is say the move you want to make. The interaction continues depending on your answer and, at the end, the player is either rescued or stuck in the enchanted forest.
Now, go to your Twilio Console dashboard, and navigate to Monitor > Logs > Calls. Then, click the entry at the top of the list, where you will find the information on the calls of the game, as shown in the picture below.
Conclusion
You've successfully used Twilio Programmable Voice and Spring Boot to create an interactive adventure game. You now know how to build captivating voice-activated apps. To further improve the user experience, try out various game mechanisms, story components, and voice interactions. Have fun with coding!
Joy Udom is a dedicated software engineer with passion for problem-solving. She thrives on seeing projects through from scratch to finish and enjoys delving into the intricacies of code, explaining its functionality with enthusiasm.
Related Posts
Related Resources
Twilio Docs
From APIs to SDKs to sample apps
API reference documentation, SDKs, helper libraries, quickstarts, and tutorials for your language and platform.
Resource Center
The latest ebooks, industry reports, and webinars
Learn from customer engagement experts to improve your own communication.
Ahoy
Twilio's developer community hub
Best practices, code samples, and inspiration to build communications and digital engagement experiences.