How to Build a Trivia Game For Your Friends Using Java and SMS

October 24, 2023
Written by
Contributor
Opinions expressed by Twilio contributors are their own
Reviewed by
Twilion

header - How to Build a Trivia Game For Your Friends Using Java and SMS

Header photo credits to SantosSocialClub.

What better way to unite attendees and engage with fans before a music festival than with a lively and engaging music trivia contest? Using Twilio's SMS API, you can quickly develop and host a music trivia game that is sure to keep your guests interested and delighted whether you're holding a small gathering or a big festival.

This article will go over the procedures for building a Twilio-enabled music trivia game for your festival group, including how to set up the game, develop the questions, and oversee the game while it is taking place, using Twilio’s Programmable API Suite. So, get ready to make your festival even more memorable with a fun and exciting music trivia game powered by Twilio!

In this music trivia game, participants respond to music-related questions. Questions on musicians, songs, albums, genres, and other relevant topics could be asked of the participants. The game is won by the person who correctly answers the most questions.

Prerequisites

  • IDE such as IntelliJ IDEA.
  • A basic understanding of Java and Spring Boot or willingness to learn.
  • An understanding of MySQL databases or willingness to learn.
  • Postman to test the game.

Set up the project

You need to ensure that your project is set up properly before starting to create the app. Start by creating a new Spring Boot project using the Spring Initializr. In the Project selection menu, select "Java" and then "Maven." Select Spring Boot 3.0.5 as it is the most recent and stable version as of this article's authoring.

The application Artifact should be renamed to something such as "musiktrivia" in the Project Metadata. This name will be used as the display title and entrance title for the project. Include a project description if you think it will help you plan more effectively. Please note that, if you want to have the same import statements as I do, you would have to use the same name in the Project Metadata. This would help you achieve import conventions such as:

import com.twilio.trivia.x

With Java 17 and the built-in Tomcat server that Spring Boot offers, choose "Jar" as the packaging type for the application.

Click the Generate button at the bottom of the page and the browser will download a .zip file with the project's boilerplate code. Now you can launch your IDE and open the project. Make sure the pom.xml file for your project contains the following dependencies. To add them to the file, simply copy and paste.

<dependencies>
    <dependency>
            <groupId>com.twilio.sdk</groupId>
            <artifactId>twilio</artifactId>
            <version>7.55.1</version>
    </dependency>
    <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
    </dependency>
    <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.20</version>
                <scope>provided</scope>
            </dependency>
    <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
    </dependency>
    <dependency>
                <groupId>jakarta.persistence</groupId>
                <artifactId>jakarta.persistence-api</artifactId>
                <version>3.1.0</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
</dependencies>

Save the modifications to the file.

Find the little icon with a "M" shape in the top-right corner of IntelliJ IDEA. Click it to load Maven modifications.

An icon with an "M" shape with a tooltip "Load Maven Changes"

Set up the MySQL database

The database will be a MySQL database, for storing objects and events of the trivia game app. Please note that you can use any database provider of your choice. Open your favorite SQL Client Tool - in my case, it is the MySQL Workbench.

Preparing to create a database relation for your project on MySQL WorkBench

In the workbench, create a table with any name of your choice. In my case, the table is named "twilio-db". Save and exit the tool.

Enter the DB relation name, to create one

Navigate to the  resources subfolder in your project directory and open the application.properties file. Paste the following information into it:

spring.datasource.url=jdbc:mysql://localhost:3306/twilio-dev?serviceTimezone=UTC
spring.datasource.username=<YOUR_DATABASE_USERNAME>
spring.datasource.password=<YOUR_DATABASE_PASSWORD>
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5Dialect
spring.jpa.hibernate.ddl-auto=update

spring.datasource.url specifies the URL for connecting to the MySQL database. In this case, it is set to jdbc:mysql://localhost:3306/twilio-db?serviceTimezone=UTC. Here, jdbc:mysql://localhost:3306 indicates that the application will connect to a MySQL database running on the local machine (localhost) on port 3306. /twilio-db is the name of the database being used, and ?serviceTimezone=UTC sets the timezone to UTC.

spring.datasource.username specifies the username used to authenticate with the MySQL database.

spring.datasource.password specifies the password used to authenticate with the MySQL database.

spring.datasource.driver-class-name specifies the JDBC driver class name for MySQL. In this case, it is set to com.mysql.cj.jdbc.Driver, which is the driver class for MySQL.

spring.jpa.properties.hibernate.dialect specifies the Hibernate dialect to be used with MySQL. It is set to org.hibernate.dialect.MySQL5Dialect, which indicates the use of the MySQL 5 dialect.

spring.jpa.hibernate.ddl-auto configures the behavior of Hibernate's schema generation tool. Setting it to "update" means that Hibernate will attempt to update the database schema automatically based on the entity mappings. If the schema doesn't exist, it will be created. If it exists, Hibernate will update it to match the entity mappings, but existing data will not be modified.

Your database is now set up and ready for development.

Initialize the port

By default, Spring Boot projects run on port 8080. However, you can change this port to a value of your choice. In the case of this tutorial, we set the port to 9090. Here’s how to do it in the application.properties file. Paste the following text into the file:

server.port=9090

Create project models

To interact with the trivia game, a number of entities are required. Essentially, there has to be people playing the game. There are also questions to be answered and a way to keep track of the score so that an eventual winner can be determined.

Create the User Model

Under the model subfolder, create a file called User.java. The code below should be copied and pasted into the newly generated file. This model stores information about each user playing the game, including their name, score, and any other relevant data.

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class User {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;

        @Column(nullable = false)
        private String name;

        @Column(nullable = false)
        private String phoneNumber;
}

@Getter and @Setter: These annotations are part of the Lombok library and automatically generate getter and setter methods for the private fields in the class. In this case, it generates getters and setters for the fields id, name, and phoneNumber. These methods allow accessing and modifying the values of the class attributes.

@AllArgsConstructor: This annotation is also from the Lombok library and generates a constructor that accepts arguments for all the fields in the class. In this case, it generates a constructor with parameters for id, name, and phoneNumber. This constructor allows creating an instance of the class with all the necessary data.

@NoArgsConstructor: This annotation, also from Lombok, generates a constructor with no arguments. In this case, it creates a constructor without any parameters. This constructor allows creating an instance of the class without providing initial values for the fields. It can be useful in certain scenarios when you want to create an empty instance and later set the values using the generated setters.

The class itself has the following fields:

name, phoneNumber: These fields represent the attributes of the User entity. They are annotated with @Column(nullable = false), which means that these fields cannot be null when persisting the User object in the database. The name field is of type String, the phoneNumber field is also a String, and the "score" field is an integer.

Create the Question model

Create a file called Question.java in the model subfolder, just like you did with the user model. Into the newly generated file, copy and paste the following code snippet below. This model stores information about each question in the trivia game, including the question itself, answer options, correct answer, and any other relevant data.

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Entity
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class Question {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;

        @Column(nullable = false)
        private String questionText;

        private int correctAnswer;

        private String gameId;
}

questionText: This String field represents the text of a question. It is annotated with @Column(nullable = false), indicating that it cannot be null when persisting the object in the database.

correctAnswer: This field represents the index of the correct answer among any four options. It is an integer and does not have any annotations associated with it. The value of this field should be between 1 and 4, corresponding to the options mentioned above.

gameId: This field represents the identifier of the game to which the question belongs. It is a String and does not have any annotations associated with it. This field is likely used to associate the question with a specific game in the system.

Create the Game model

This model would store information about each game session, including the start time, end time, game ID, and any other relevant data. Create a file called Game.java in the model subfolder, copy and paste the following code snippet below.

import jakarta.persistence.*;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.time.LocalDateTime;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Entity
public class Game {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;

private boolean isOngoing = true;

        @Column(nullable = false)
        private LocalDateTime startTime;

        @Column(nullable = false)
        private LocalDateTime endTime;
}

isOngoing: This field indicates that the game, which has just been created, is on-going. When the game ends, this field is set to false, specifying that the game is no longer being played.

startTime: This field represents the starting time of an event or activity. It is of type LocalDateTime, which is a class in Java that represents a date and time without any time zone information. The @Column(nullable = false) annotation indicates that this field cannot be null when persisting the object in the database. Therefore, a valid value for startTime must be provided.

endTime: This field represents the ending time of an event or activity. It is also of type LocalDateTime and is annotated with @Column(nullable = false). Similar to startTime, this annotation indicates that endTime cannot be null when persisting the object in the database. It is required to provide a valid value for endTime.

Create the real-time data sync model

Finally, in the same subdirectory where other models have been created, create a file named RealTimeData.java and paste in the code below. This model is responsible for managing the real-time data synchronization between different players in the game, including updating the scores and game progress in real-time as players answer questions.

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Entity
public class RealTimeData {

        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long Id;

        @ElementCollection
        private List<Long> playerIds = new ArrayList<>();

        @ElementCollection
        private Map<Long, Integer> scores = new HashMap<>();

        private String gameId;

private int winnerId = 0;
}

playerIds: This field represents a collection of player emails. It is annotated with @ElementCollection. This annotation indicates that the field is a collection of simple values (in this case, Long values) that will be stored separately from the main entity. It allows for mapping a collection of values without the need for a separate entity. In this case, it represents a list of player IDs stored in the "playerIds" field. The initial value of the field is an empty ArrayList.

scores: This field represents a mapping between player IDs (Long) and their respective scores (Integer). It is annotated with @ElementCollection. This annotation signifies that the field is a collection of key-value pairs that will be stored separately from the main entity. In this case, it represents a mapping between player IDs and their scores stored in the scores field. The initial value of the field is an empty HashMap.

gameId: This field represents the identifier of the game. It is of type String and does not have any annotations associated with it. This field is likely used to uniquely identify the game associated with the entity.

winnerId: This field represents the identifier of the winner of the game

Create the CreateQuestionRequest model

In the model directory, create a file named CreateQuestionRequest.java and paste the code snippet below in this file. The purpose of this class is to receive requests for creating a question object.

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class CreateQuestionRequest {

        private String questionText;

        private String gameId;

        private int correctAnswer;

}

Build the repositories

Create four new files under the repository subdirectory: UserRepository.java, GameRepository.java, RealTimeDataRepository.java and QuestionRepository.java. Paste the following code snippets below in each of their respective files:

Implement UserRepository

package com.twilio.trivia..repository;

import com.twilio.trivia.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {

}

package com.twilio.trivia.model.User.repository;

import com.twilio.trivia.model.Game;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface GameRepository extends JpaRepository<Game, Long> {

}

package com.twilio.trivia.repository;

import com.twilio.trivia.model.RealTimeData;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface RealTimeDataRepository extends JpaRepository<RealTimeData, Long> {
        
RealTimeData findByGameId(Long gameId);

}

package com.twilio.trivia.repository;

import com.twilio.trivia.model.Question;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface QuestionRepository extends JpaRepository<Question, Long> {

        Question findByGameId(String gameId);
  
}

Build the services

The business logic for how the user will interact with the app is implemented in this section.

Integrate the Twilio API

The logic for communicating with the Twilio Programmable SMS API must be implemented by the TwilioConfigService.java class within the service package. Always avoid directly displaying your Twilio credentials or any other sensitive information in this or any other file. This may be done in a variety of ways. Declaring your environmental variables in the application.properties (or application.yml) file is nonetheless a popular strategy.

Go to the application.properties file in the resources directory and paste the configuration below into it:

#... Other environmental variables

#Twilio Config
ACCOUNTSID=<ACCOUNT_SID>
AUTHTOKEN=<ACCOUNT_TOKEN>
FROMNUMBER=<YOUR_TWILIO_NUMBER>

Paste the following code snippet into the TwilioConfigService.java file.

import com.twilio.Twilio;
import com.twilio.rest.api.v2010.account.Message;
import com.twilio.type.PhoneNumber;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

@Service
@Slf4j
public class TwilioConfigService {

        @Value("${ACCOUNTSID}")
        private String accountSID;

        @Value("${AUTHTOKEN}")
        private String authToken;

        @Value("${FROMNUMBER}")
        private String fromNumber;

        public void sendMessage(String toNumber, String messageBody) {
            Twilio.init(accountSID, authToken);
            Message message = Message.creator(new PhoneNumber(toNumber),
                    new PhoneNumber(fromNumber), messageBody).create();
        }
}

The sendMessage() function requires two parameters: toNumber, which is the receiver's phone number, and messageBody, which is the text message that will be delivered to the recipient.

The accountSID and authToken are used to initialize the Twilio API client using the Twilio.init() function. The Message.creator() function generates a new SMS message with the message body, sender phone number, and receiver phone number specified. The new() function transmits the message and returns a Message object with the message's specifics, such as the SID.

You must update the default values for accountSID, authToken, and fromNumber with your Twilio account SID, authentication token, and phone number from the Twilio dashboard in order to utilize this TwilioService class. You can then use the Twilio API to send SMS messages by creating an instance of this class in your application and calling the sendMessage() function.

Implement the Trivia Services

This class is responsible for starting, playing and ending a game. In the Service package, create a file named TriviaGameService.java and paste the code below in it. The full class and its explanation are given below:

import com.twilio.trivia.model.*;
import com.twilio.trivia.repository.GameRepository;
import com.twilio.trivia.repository.QuestionRepository;
import com.twilio.trivia.repository.RealTimeDataRepository;
import com.twilio.trivia.repository.UserRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.Map;

@Service
@Slf4j
public class TriviaGameService {

        @Autowired
        private UserRepository userRepository;

        @Autowired
        private QuestionRepository questionRepository;

        @Autowired
        private GameRepository gameRepository;

        @Autowired
        private RealTimeDataRepository realTimeDataRepository;

        @Autowired
        private TwilioConfigService twilioConfigService;

        public User createUser(User user) {
            return userRepository.save(user);
        }

        public Game startGame() {
            Game game = new Game();
            game.setStartTime(LocalDateTime.now());
            game = gameRepository.save(game);

            RealTimeData realTimeData = new RealTimeData();
            realTimeData.setGameId(game.getId());
            realTimeDataRepository.save(realTimeData);

            return game;
        }

        public RealTimeData checkGameData(Long gameId) {
            return realTimeDataRepository.findByGameId(gameId);
        }

        public String addUserToGame(Long userId, Long gameId) {

            RealTimeData realTimeData = realTimeDataRepository.findByGameId(gameId);
            realTimeData.getPlayerIds().add(userId);
            realTimeData.getScores().put(userId, 0);
            realTimeDataRepository.save(realTimeData);

            return "User has been successfully added";
        }

        public Question createQuestion(CreateQuestionRequest createQuestionRequest) {

            Question question = new Question();
            question.setQuestionText(createQuestionRequest.getQuestionText());
            question.setCorrectAnswer(createQuestionRequest.getCorrectAnswer());
            question.setGameId(createQuestionRequest.getGameId());

            // send question to all game players
            RealTimeData realTimeData = realTimeDataRepository
                    .findByGameId(Long.valueOf(createQuestionRequest.getGameId()));

            for (Long userId : realTimeData.getPlayerIds()) {
                String userPhoneNumber = userRepository.findById(userId).get().getPhoneNumber();
                twilioConfigService.sendMessage("+234" + userPhoneNumber.substring(1),
                        createQuestionRequest.getQuestionText());
            }

            log.info(String.format("message sent: %s", createQuestionRequest.getQuestionText()));

            return questionRepository.save(question);
        }

        public String sendAnswer(Long userId, String gameId, int correctAnswer) {

            Question question = questionRepository.findByGameId(gameId);

            if (question.getCorrectAnswer() != correctAnswer) {
                return "Wrong answer. Thank you for trying";
            }

            RealTimeData realTimeData = realTimeDataRepository.findByGameId(Long.valueOf(gameId));

            int currentScore = realTimeData.getScores().get(userId);
            int newScore = currentScore + 1;
            realTimeData.getScores().put(userId, newScore);
            realTimeDataRepository.save(realTimeData);

            return "You are correct. Well done!";
        }

        public void endGame(Long gameId) {

            Game game = gameRepository.findById(gameId).orElse(null);
            assert game != null;
            game.setEndTime(LocalDateTime.now());
            game.setOngoing(false);

            gameRepository.save(game);
        }
        String userWithHighestScore = null;

        public String sendWinnerNotification(String gameId) {
            Long winnerId = null;

            RealTimeData realTimeData = realTimeDataRepository.findByGameId(Long.valueOf(gameId));
            Map<Long, Integer> realTimeDataScores = realTimeData.getScores();

            int highestScore = Integer.MIN_VALUE;

            for (Map.Entry<Long, Integer> entry : realTimeDataScores.entrySet()) {
                Long userId = entry.getKey();
                int score = entry.getValue();

                if (score > highestScore) {
                    highestScore = score;
                    userWithHighestScore = userRepository.findById(userId).get().getName();
                    winnerId = userId;
                }
            }

            String message = String.format("Hello there. Player with ID %s has won the trivia. Congratulations to " +
                    "them. Thanks for playing", winnerId);

            for (Long userId : realTimeData.getPlayerIds()) {
                String userPhoneNumber = userRepository.findById(userId).get().getPhoneNumber();
                twilioConfigService.sendMessage("+234" + userPhoneNumber.substring(1), message);
            }
            log.info(message);

            return "Notification sent";
        }

}

The class is annotated with @Service, indicating that it is a Spring service component.

The service has several dependencies autowired using the @Autowired annotation, including repositories such as UserRepository, QuestionRepository, GameRepository, and RealTimeDataRepository. Additionally, there is a dependency on TwilioConfigService.

The createUser() method creates a new user by saving the provided user object using the UserRepository.

The startGame() method initializes a new game by creating a new Game object, setting the start time using LocalDateTime.now(), and saving it to the GameRepository. It also creates a new RealTimeData object associated with the game and saves it to the RealTimeDataRepository.

The addUserToGame() method adds a user to a specific game by updating the RealTimeData object associated with the game in the RealTimeDataRepository. It adds the user ID to the list of player IDs and initializes their score to 0.

The createQuestion() method creates a new question by mapping the request data to a Question object and saving it to the QuestionRepository. It also sends a message to all players in the game using the twilioConfigService.sendMessage() method.

The sendAnswer() method handles a player's answer to a question. It retrieves the corresponding question from the QuestionRepository, checks if the answer is correct, updates the player's score in the RealTimeData object, and marks the question as answered.

The endGame() method marks the game as ended by retrieving the game object from the GameRepository and updating its end time and ongoing status.

The sendWinnerNotification() method sends a notification to all players in the game, notifying them about the winner. It retrieves the scores from the RealTimeData object, determines the player with the highest score, sends a message to all players using the twilioConfigService.sendMessage() method, and returns a notification message.

Build the controller

In the root package of your project, create a project named controller. Inside it, create a file with the name TriviaGameController.java. Paste the code below into it:

import com.twilio.trivia.model.CreateQuestionRequest;
import com.twilio.trivia.model.Game;
import com.twilio.trivia.model.RealTimeData;
import com.twilio.trivia.model.User;
import com.twilio.trivia.service.TriviaGameService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api")
public class TriviaGameController {

        @Autowired
        private TriviaGameService triviaGameService;

        @PostMapping("/create-user")
        public ResponseEntity<?> createUser(@RequestBody User user) {
            return new ResponseEntity<>(triviaGameService.createUser(user), HttpStatus.OK);
        }

        @PostMapping("/game/add-user/{userId}/{gameId}")
        public ResponseEntity<?> addUserToGame(@PathVariable Long userId, @PathVariable Long gameId) {
            return new ResponseEntity<>(triviaGameService.addUserToGame(userId, gameId), HttpStatus.OK);
        }

        @PostMapping("/start-game")
        public ResponseEntity<Game> startGame() {
            Game game = triviaGameService.startGame();
            return new ResponseEntity<>(game, HttpStatus.OK);
        }

        @GetMapping("/see-game/{gameId}")
        public ResponseEntity<?> seeGameLiveData(@PathVariable Long gameId) {
            RealTimeData game = triviaGameService.checkGameData(gameId);
            return new ResponseEntity<>(game, HttpStatus.OK);
        }

        @PostMapping("/send-question/")
        public ResponseEntity<?> sendQuestion(@RequestBody CreateQuestionRequest createQuestionRequest) {

            return new ResponseEntity<>(triviaGameService.createQuestion(createQuestionRequest), HttpStatus.OK);
        }

        @PostMapping("/submit-answer/{userId}/{gameId}/{correctAnswer}")
        public ResponseEntity<?> submitAnswer(@PathVariable Long userId,
                                                 @PathVariable String gameId,
                                                 @PathVariable int correctAnswer) {

            return new ResponseEntity<>(triviaGameService.sendAnswer(userId, gameId, correctAnswer), HttpStatus.OK);
        }

        @PostMapping("/end-game/{gameId}")
        public ResponseEntity<?> endGame(@PathVariable Long gameId) {

            triviaGameService.endGame(gameId);
            return new ResponseEntity<>(HttpStatus.OK);
        }

        @PostMapping("/send-notification/{gameId}")
        public ResponseEntity<?> concludeGame(@PathVariable String gameId) {
            return new ResponseEntity<>(triviaGameService.sendWinnerNotification(gameId), HttpStatus.OK);
        }
}

The class is annotated with @RestController, indicating that it is responsible for handling incoming requests and returning responses in a RESTful manner.

The @RequestMapping("/api") annotation defines the base URL path for all the endpoints defined in this controller.

The controller has a dependency on the TriviaGameService class, which is autowired using the @Autowired annotation. This allows the controller to utilize the services provided by the TriviaGameService for handling requests.

The controller defines several request mapping methods to handle different types of requests:

The createUser() method is mapped to the HTTP POST request with the path "/create-user". It expects a User object in the request body and returns a ResponseEntity containing the response body and HTTP status.

The addUserToGame() method is mapped to the HTTP POST request with the path "/game/add-user/{userId}/{gameId}". It extracts the user ID and game ID from the path variables and returns a ResponseEntity containing the response body and HTTP status.

The startGame() method is mapped to the HTTP POST request with the path "/start-game". It starts a new game by invoking the startGame() method from the TriviaGameService and returns a ResponseEntity containing the response body (a Game object) and HTTP status.

The sendQuestion() method is mapped to the HTTP POST request with the path "/send-question/". It expects a CreateQuestionRequest object in the request body and returns a ResponseEntity containing the response body and HTTP status.

The submitAnswer() method is mapped to the HTTP POST request with the path "/submit-answer/{userId}/{gameId}/{correctAnswer}". It extracts the user ID, game ID, and correct answer from the path variables and invokes the sendAnswer() method from the TriviaGameService.

The endGame() method is mapped to the HTTP POST request with the path "/end-game/{gameId}". It extracts the game ID from the path variable and invokes the endGame() method from the TriviaGameService.

The concludeGame() method is mapped to the HTTP POST request with the path "/send-notification/{gameId}". It extracts the game ID from the path variable and returns a ResponseEntity containing the response body and HTTP status.

The methods in the controller delegate the actual processing of the requests to the corresponding methods in the TriviaGameService by invoking the appropriate methods and passing the necessary parameters.

The responses from the TriviaGameService methods are wrapped in ResponseEntity objects, allowing customization of the response body and HTTP status codes.

Test your SMS trivia game

The code for this game can be found in this GitHub repository.

The app will now be tested to ensure that it performs as intended. Click the green start button in the top-right corner of your IDE to launch the project. On the terminal of your IDE, you ought to see a notification that says "Started application..."

Try out the endpoints established in the controller class by opening Postman. API testing may be done with Postman. Create a fictitious user, begin a game, add users to it, set the questions, receive entries as answers, get and announce a winner.

Create a user

Paste the URL "http://localhost:9009/api/create-user" in the URL field of the Postman program after starting it up. Select the 'Body' tab. pick 'raw' from the alternatives listed beneath it, and then click the dropdown arrow to the extreme right of the same list to pick 'JSON'. Paste the following text into the available space and make a POST request to the URL:

{
        "name": "Test User",
        "phoneNumber": "08123456789"
}

Push the send button, then. The creation of a user would be successful.

Creating a user (game player) via Postman

Start a game

To start a game, paste the URL in the text box and click send or press Enter; making a POST request in order to start a game. A Game object should be created now.

Starting a new musiktrivia game.

Add users to the game

Follow the steps above to create a user; add a number of users. For sake of brevity, I will use two pre-added users, with IDs 2 and 3 respectively.

Now, add the two new users to the game. Paste the URL into the URL box: "http://localhost:9009/api/game/add-user/{userId}/{gameId}" . Replace the placeholders (userId and gameId) with their actual values.

Adding users to the game just started

In the image above, we added a user with the ID 4 to the game with the ID 2. You can add more users with the same approach. I have gone ahead to add two more users to the game - users ID 2 and ID 3.

In the TriviaGameService.java, we wrote that, when a game is started, a RealTimeData object is also created at that time. To confirm that this happened, paste the following URL in the Postman URL text box ("http://localhost:9009/api/see-game/<gameId>") and make a GET request. Remember to replace the gameId with the actual value. You see the two players we added to the game and their score counts:

Game started, with the three players scheduled to play it.

Send questions to players

Now, time to put out the question. Paste the following in the URL box "http://localhost:9009/api/send-question/>" and then paste this following request in body section:

{
   "questionText": "Q. Burna Boy is \n, 1. Ghanaian \n 2. Nigerian 3. French \n 4. British",
   "gameId": 1,
   "correctAnswer": 2
}

Make a POST request.

Sending question to game players for them to respond to

A display of text message received by one of the players

Send an answer to the question

This time, the players get to respond to the question by sending in their preferred answers. Recall that our game has two players. We will assume that one player got the answer correctly and the other was wrong. Then we check the real time data for the game for updates. First, paste the URL in the text box:

"http://localhost:9009/api/submit-answer/{userId}/{gameId}/{correctAnswer}". Replace the placeholders in the URL with their actual values.

First we will test the incorrect answers from players with userId 2 and 3.

Sent in answer. Wrong answer has been provided.

Sent in answer. Wrong answer has been provided.

Then a correct answer from a player with userId 4.

Correct answer provided to trivia question

Now, check the real time data to see that there is progress in the game. The player with ID "2" should have a score of 1 and the player with ID "1" should still remain at 0. Go to "http://localhost:9009/api/see-game/<gameId>" and make a GET request.

Finish the game and send the winner update

The game can continue for as long as it is wanted. At the end, a winner should emerge and a notification should be sent to all players, as shown below. Paste the URL in the text box "http://localhost:9009/api/send-notification/{gameId}". Replace the placeholder with the actual value for the game ID and make a POST request.

End game and send notification to all players with the winner of the trivia.

Display of congratulatory message sent to all players

What's next for SMS trivia games in Java?

You will need a model to store information about the music tracks or artists featured in the game, or a model to store information about the festival group hosting the game.

Creating a successful music trivia game requires careful consideration of various factors, such as the difficulty level of the questions, the format of the game, and the prizes or rewards for winners. It's important to strike a balance between challenging questions and questions that are accessible to everyone. Additionally, the format of the game should be direct; whether it's a traditional quiz format or a more interactive game that involves physical challenges or team collaboration.

Building a music trivia game for your festival group can be an excellent way to enhance the overall festival experience. Happy coding.

Tolulope is a growing software engineer who loves coding and writing about what he does. When he's not coding, he enjoys his time as a disc jockey.