August 30, 2023

The Making of Temporal Trivia

Solution Architect Team

Solution Architect Team

This project and post was a true team effort and was written by a handful of people from the Solution Architect Team: Ci-Ci Thomson, Anthony Wong and Keith Tenzer

Introduction

In Spring of 2023, Temporal held its second annual Hackathon. Leadership challenged us to build “cool stuff” and maybe leverage ChatGPT. As Solution Architects at Temporal, we sadly don’t get to code as much as we would like, in other words this was a perfect opportunity!

We gathered together a team, including Ci-Ci Thomson, Anthony Wong and Keith Tenzer and came up with the idea to build a game using Temporal. In the end, a game is really just some state and logic which can be expressed as a workflow, right? So, we decided to make a Trivia Game and incorporate ChatGPT for question generation.

Our design goals:

  • Scale to millions of users
  • Support multiplayer and unlimited number of games
  • Build using polyglot
  • Maintain game state and durability when things break
  • Timeout mechanism for questions
  • Single source of truth for game logic
  • Do it all in a few hundred lines of code!

How the Game Works

Getting into the thick of it, let’s walk through everything. Players can create or join a game. Category, number of players, number of questions and even a challenge mode are all configurable. Once the game starts, players are presented with questions and multiple choice answers. They submit their answers and results are shown including a final score once the game completes.

Backend Infrastructure

Temporal Trivia implemented the game as two workflows: GameWorkflow and AddPlayerWorkflow.

The GameWorkflow is launched when a player creates a game. It waits for players to join and starts the game. When a player creates or joins a game, that player is added to the game by the AddPlayerWorkflow.

Once the game is started, trivia questions and answers are gathered using ChatGPT. The GameWorkflow then proceeds through the game loop, gathering player submissions and keeping track of scoring. Once the game is completed, the final scores are displayed.

Activity

Trivia questions and answers are gathered using an activity which executes an API call to ChatGPT. Player moderation executes an activity to validate player names before adding them to the game via the AddPlayerWorkflow. Final scores are calculated using an activity.

Queries

Game state is exposed externally via queries by the GameWorkflow. The GameWorkflow provides three queries: getPlayers, getProgress and getQuestions. Queries are used by the AddPlayerWorkflow and the UI.

Signals

Players are added to the GameWorkflow and submit their answers via signals. Since signals are ordered, we give an extra point to the player who answers the question correctly first. This makes game play more interesting.

Timers

The GameWorkflow progresses between its phases and questions using configurable timers. In addition, a timer is also used to start the game if enough players don’t join.

Backend Diagram

backend diagram

Frontend Version 1

In Version 1, we had players join the game via SMS and make one player host the game on their computer to show off the Lobby, Questions, Answers, and Results. The main driver for this is because of the time constraints. Not only that, but we were also learning how to use the Svelte. The front end was straightforward — keep querying against the Temporal Workflow to see what the front-end should display. Both the SMS and SvelteKit are connected to the same NodeJS Express Server. The NodeJS Express Server kept track of what game was going on and which player was in which game.

trivia-workflow-sa

Frontend Version 2

In Version 2, we added browser support. This allowed both desktop and mobile users to join in the game. Each player can see the Lobby, Questions, Answers, and Results in their own devices. We had to use a new framework for this because we wanted to show this off at a conference. Due to time constraints, we have built this out using Python and Flask. The V2 Frontend offers the following features:

  • Player name
  • Join in an open game
  • Create a new game and specify the parameters
  • How many questions, players, etc.

Key Learnings

As mentioned earlier, this was a part of an internal hackathon so many of the concepts we wanted to implement we had never done before, and learning is the main point of a hackathon! The three of us all learned a great deal relating to the parts we coded as well as the best way to work as a team by agreeing on an overall design purposefully upfront to allow for ease of collaboration.

Keeping Logic in One Place

In general, Temporal allowed us to keep all of the game logic in one place (workflows) and allowed for clean and decoupled UI design. This also allowed us to each choose our language of choice to implement our respective pieces of the project. We found that even though the backend workflows are written in Go and the frontend is written in Typescript, the workflow interactions between languages are seamless (and there were no issues with data serialization/deserialization).

Two Workflows are Better than One

The initial design of the application had a single workflow that handled both the game and players. After further analysis, we decided to break this into two workflows, one for the GameWorkflow and one for AddPlayerWorkflow. Interactions were then handled via a single gameId. This decision allowed for simplified logic and interactions as well as allowing different team members to work more independently. One of the main learnings we had was that we initially did not cancel the timers properly at first and this caused the timers to unexpectedly fire later and break the game loop.

Moderation is Key

We also had intended to implement name moderation using the ChatGPT moderation functionality. It turns out that moderation is intended for longer strings of text and intent instead of filtering out profanity words. We then found a service specifically meant for profanity filters (https://www.purgomalum.com/) and were able to replace ChatGPT by only changing the call to the moderation service.

Iteration and Small Pieces make an MVP at each stage

Version 1 of the trivia game had all players using their phone to join a game and answer questions by texting a specific phone number. Version 2 of the game has been upgraded to have a web interface for users to join games and answer questions. We already have a few upgrades in mind for Version 3 of the game. For instance, instead of having a player lobby where a player joins a game, we plan to use matchmaking to have players automatically assigned to a game.

Each iteration has the potential to be more efficient

Currently, we use regex parsing to process the ChatGPT chat stream of questions and answers. We plan on simplifying this functionality by having ChatGPT return JSON instead of plain text to get rid of the overhead of regex. The biggest upgrade we plan on for Version 3 is one that has been waiting on new Temporal functionality that has just been released. There is quite a bit of querying and signaling into the Workflow and this is not efficient. We can enhance the performance of the entire game by changing these to a synchronous Workflow Update (link to doc)

In the front end, there has been multiple discussions about the trade-off between polling and websocket. We used the polling method because of the ease of use and limited time. It is still worthwhile exploring websocket as we are looking to build Version 3 of this, which includes Matchmaking.

Not only that but a different front-end stack. In Version 1, we used SvelteKit. It was our first time using it, and after using another front-end framework like React, it is a lot easier to use. The routing was super easy, and building components.

Conclusion

If you like what you have seen and read and are interested in either trying it out or implementing something similar, here are the Github links:

We have a hosted version you can play, we'd love to hear what you think!

triviav2.tmprl-demo.cloud

If you want to dig into the code...

The team is happy to join a Zoom call for a more in-depth discussion of how this was designed and implemented, or if you just have specific questions, feel free to reach out in any of the following methods!

💬 Temporal Community Slack

Replay Hackathon

More importantly, did you hear? We are doing ANOTHER hackathon, and this time, we are inviting you, our community, to participate in it, along with Replay. You can check out some details here on another blog post, or if you want to go ahead and try it, feel free to sign up under the tickets section of our Replay site—use HACK20 for 20% off!