Enter the (Gluck) Arena
Background
One of my favorite party games is Quiplash from Jackbox Games. Jackbox games are unique because they leverage the fact that (almost) everyone has a smartphone, and leveraging them to create a unique game experience. However, it still requires a computer/console and a display to play, which makes it hard to use when traveling. In 2024, me and my wrestling club teammates tried to set up a game after our national tournament, but weren't able to get it set up in our hotel room due to the previously mentioned issues. Though we ended up finding a russian cards against humanity clone to play, it planted the seed for me to create Gluck Arena, a jackbox style website that doesn't need a computer and display to play games. In this blog, I'll go over how Gluck Arena works along with Quizzer, a quiplash style party game that I use Gluck Arena to play.
How Gluck Arena Works
Technical Architecture
Gluck Arena is mobile first website designed for arbitrary multiplayer party games. It was designed for party games and doesn't require players to create accounts. At a high level, it was built with the following technologies:
- Typescript: A typed superset of Javascript with static typing
- Frontend: Serverless NextJS application hosted on Cloudflare Pages
- Backend: Convex as the real-time backend as a service (BaaS) platform
The multiplayer features leverage Convex's sync engine, which ensures all players see the most recent game state. This architecture allows for seamless multiplayer experiences without complex state management, as long as the games don't require extremely frequent updates that could cause conflicts between players.
Key Components
Player Management
When a player first enters Gluck Arena, a unique ID is created for them as their "account" and stored in a browser's session storage. The below form then appears asking to enter a username that will be used for their time in Gluck Arena.
Once a username is chosen the player ID and username are added to "players" table in the Convex database. The username can be changed at anytime using the "change name" form in The navigation bar. If a player refreshes the page or leaves and re-enters the browser session, the unique ID from the session will then be used to check the database for a corresponding player. If there is no ID in session storage or if the ID isn't found in the player table, a new ID is created and the player created another "account."
Game Management
Each game instance in Gluck Arena is stored as a single row in the "games" table. A game consists of three properties: a join code to allow players to enter a game, a type to represent what type of game is being played, a status to show if a game is joinable/in-progress/completed, and a state that contains the information needed to play the game. There is also a "game-player" table used to track which players are in which games.
Convex allows documents, or "rows" in a table to contain a maximum of 1 mebibyte (MiB, which is slightly bigger than a megabyte) of data, 16 layers of nesting for objects, and 8192 objects per array. This makes it easy to store all the game logic in a single row in the "games" table. In addition, the game logic is typed using a Typescript union type to ensure type safety even though rows can represent different games. However, for games like Quizzer, an additional table is needed to store available questions.
To ensure the table doesn't get filled with expired games and old players, a Cron Job is configured to delete each once they've reached predefined "oldness" thresholds based on activity and age. Once a game or player is deleted, a database trigger is activated to delete rows in the "game-player" table that reference it.
Quizzer Implementation
To implement a game in Gluck Arena, there are 5 requirements: a type to represent the game logic, backend functions to allow game actions, and a React page for each of the game statuses (lobby, active, and finished). The lobby page is shown when a game is created, but waiting for all players to join. There are also typically settings in this page shown to the game creator for configuring the game for things such as number of rounds to play. The active page is used to play the game and the finished page shows the leaderboard of who won. Because implementing a game is so standardized, AI tools like Cursor can be used to implement a lot of these requirements.
The rules of Quizzer are simple. Each round, players answer questions assigned to them such that each question is answered by exactly 2 players. Then, players vote on which answer was better for each question (except for the answer writers) and players get points depending on how many votes their answer got. At the end of the game, whichever player gets the most points wins the game.
There are 2 types of questions in Quizzer: the default questions (which I put in the database) and AI generated questions (which are dynamically generated per game). Before each game, the game creator has the option to allow AI questions and enter in a prompt to give a theme to the question if entered. The creator can also specify how many questions they need, allowing a mix of AI and non-AI questions. The creator can also add custom questions by hand through a UI in the lobby page.
For each Quizzer game, there are questions per round where n is the number of players. When a game is created, it randomly selects from the question database and starts the game. It then divides the questions up into rounds and assigns each question to 2 players. At the start of each round, players then go into "question answering mode" where they answer their assigned questions. If players are taking too long to answer questions, the game creator has the ability to end this mode early and AI generate answers to unanswered questions. After this mode is over, players vote on prompts with the game creator controlling the speed at which the game is played. This is repeated for each of the rounds in the game.
Conclusion
One thing that makes Gluck Arena stand out from my other projects is in how it's based on the features/limits of Convex. Without Convex, the multiplayer features would be significantly more complex to implement and likely more expensive as well. This project reminded me of Warpstream, a Apache Kafka compatible product that simplifies and cheapens stream processing by leveraging the features/limits of object storage. I recently wrote a report on the patentability of Warpstream for a class that goes into more detail if you'd like to learn more.
Overall, I believe that the biggest takeaway from this project is the power of designing projects around specific technologies. I feel there is a lot of unneeded pressure in software engineering to make projects backend agnostic and infinitely scalable. However, designing projects around a technology like Convex or Object Storage often has tangible benefits. This is more obvious in Warpstream's case as it was acquired for $220 million 13 months after it as released for essentially rebuilding Kafka from the ground up around modern technologies. I suspect this trend will continue as technology gets better and platforms give stronger guarantees.