6 min read
Quickdraw Versus

Quickdraw Versus is a web-based multiplayer guessing game, based on the single-player “Quick, Draw!” game developed by Google. All players are given a word to draw, and while they draw it, a machine learning model guesses what they are drawing. The first player who’s drawing is correctly guessed by the model wins the round. The player who wins the most rounds wins the game.

The tech stack for this project is as follows:

  • 💻 Front-end:
    • Next.js
    • TailwindCSS
    • DaisyUI components
    • ONNX Runtime for clientside AI inference
  • 🛠️ Back-end:
    • FastAPI
    • Redis (Pub/Sub)
    • WebSockets
    • Nginx with Let’s Encrypt for HTTPS
  • Docker Compose for easy deployment
  • PyTorch to train the AI model

I didn’t know any of these technologies beforehand, so it has been a major learning experience for me!

Starting the project

It started with the idea, which my friend cttps proposed. He and I decided to start the project together.

The beginning was mostly rough work; a lot of mess. Neither of us knew enough about the technology we were working with to put together a working prototype. But gradually, as we learned more, reading documentation and examples, and some help from an LLM or two, we started to understand how the components should fit together.

Developing knowledge

FastAPI

Our project uses FastAPI, a Python framework, as a performant and simple way to both serve static front-end files and serve WebSocket/API endpoints. In their words, FastAPI is

high performance, easy to learn, fast to code, ready for production.

I have to agree with them! It was really quite simple to pick up and get going with. I highly recommend it.

WebSockets

WebSockets are used because they allow real-time bidirectional communication between the players and servers, which regular HTTP request methods would not be suited for. Communication is done via JSON strings, each of which is given a type depending on the game event that is communicated.

Redis

Redis is used as a Pub/Sub broker and as a fast database. This is in order to make the whole application scalable. Each WebSocket server (FastAPI instance) can only hold so many websocket connections. We could increase server resources (scale “vertically”) to handle more connections, but this is expensive and not viable for huge player numbers. But if we were to scale “horizontally” by adding more servers, how would we be able to send messages to all the connected players of a game, if they were all connected on different servers?

The solution was found with Redis’ Pub/Sub—meaning publish/subscribe—model. When the server wants to broadcast a message to all players of a specific game session, it “publishes” the message to a Pub/Sub channel for that session. Each server with players connected to that session is “subscribed” to the Pub/Sub channel for that game. It then receives the published message and relays it back to all the players connected for that game through the WebSockets. This enables us to scale to many WebSocket servers, and they are all able to “communicate” via Redis.

ONNX Runtime

ONNX Runtime is a high-performance way to run AI inference clientside. This lets us skip sending the drawing data to the server entirely, leading to less resources required to run the game.

The pretrained model is sent by the server when the page is loaded and is used to create an InferenceSession. When playing, the drawing data is preprocessed into the correct format and then inference is performed on it, guessing what the player has drawn. If this guess is the current round’s word, a message is sent to the server saying the round has been won.

ONNX Runtime’s performance is impressive. In my testing experience, there was never any lag or hiccups experienced. It’s an interesting tool and definitely one I will consider in the future for any other web-based AI projects.

Challenges

React

Before starting this project, I had very little experience with front-end web development. I had done some raw HTML, CSS and JS when I was younger but that was it.

Throughout this project I learned how to use React and TailwindCSS to create the front-end. Now that I know more about it, I would go back and completely redo the front-end to structure it better and make it more maintainable, but that’s for a later day. 😅

The rather convoluted approach using React Components that we took, made some things overly complicated. It probably would’ve been a better idea to learn more about React before attempting to start a project of this complexity. I have now learned the way that components and props are supposed to be used and look forward to doing things the right way around in future projects using React.

DevOps side of things

It was my first time ever deploying a web application like this and I found the DevOps side to be quite confusing, even for a single-instance deployment. In the future I’d like to learn how to deploy with Kubernetes for example to really take advantage of the scalability we implemented.

Figuring out how to configure Nginx for HTTPS with Docker Compose was also a real challenge. I might make my own guide on how to do it because the resources I found online were not very helpful. In the end I figured out how to get it done with Let’s Encrypt/Certbot. I also needed to set it to host the static files while also acting as a reverse proxy for FastAPI.

Please, grab a friend and try it out!

Try it out here: quickdraw-vs.com