LogoNextJet
All Projects
RS Duel
5 min read
Visit Project

RS Duel

RS Duel

RS Duel

A

Renas Hassan

May 5th, 2025

Overview

RS Duel is a hobby project of mine which is heavily inspired by a childhood game I loved playing, OSRS (Old School Runescape).

I have played this game countless hours as a kid, every day after school I would rush home to play it. It's nice to see that the game is still going strong after all these years.

That nostalgia eventually sparked this project.

The problem & motivation

The duel arena in OSRS was a place where players could engage in one-on-one combat, either for fun or for staking coins. It was a much-loved feature in the game. However, after an update in 2022, it was removed from the game.

I decided to replicate the core functionality of the duel arena and revive it in my own application, so users could once again enjoy what was taken away from them.

Duel arena in old school runescape. Players doing a whip stake.
Duel arena in Old School Runescape. Players doing a whip stake.

Key features & functionality

Duel players

Players can wager money and duel each other in real-time. The odds are 50/50 and the winner takes home the entire pot.

It uses the same OSRS damage algorithm.

00:00 / 00:00

Open chests

Try your luck and open chests for random rewards.

00:00 / 00:00

Chat

Chat with all logged in players in real-time.

Chat interface to chat with users

Tech stack

A good old-fashioned separate frontend and backend was used for this project.

A dedicated backend server was needed to establish a persistent websocket connection for the real-time chat and dueling features.

Since we needed very custom and complex character animations for dueling, I found that Rive was the perfect library for the job. It allows you to create extremely cool, customized, and interactive animations.

I did get help designing the character as well as creating the idle and attack animations.

To bring these futures to life, I selected the following technologies:

  • Frontend: Vite, React, Sass, Rive
  • Backend: Node.js, Express.js, Prisma (ORM), Socket.io
  • Database: PostgreSQL
  • Code Quality: ESLint, Prettier
  • Core: TypeScript

Challenges & learnings

This project involved interesting, unconventional functionality, and thus required some out-of-the-box thinking.

The slot machine reel

00:00 / 00:00

This slot reel requires a very specific implementation to get it right.

When we click "open", it needs to:

  • Shuffle, repeat, and spin through the items, while giving the illusion of infinite spinning.
  • Land on the predetermined winning item fetched from the backend to prevent cheating.

To achieve this we can break down the problem into smaller pieces so it becomes more manageable:

  1. Create an outer and a moving inner reel.
  2. The outer reel has a fixed viewing window which only shows a limited portion of the inner reel.
  3. The inner reel is a long horizontal strip which holds all the possible chest item rewards, repeated and in randomized order.
  4. When you spin, the inner reel slides horizontally with transform: translateX(...).
  5. Because the inner reel is very wide, it gives the illusion of an infinitely spinning reel within the fixed viewing area.

I learned that sometimes, there are no libraries or documented solutions for the problem you're tackling. In those cases, you need to create your own implementation.

This can be quite daunting when you think of the problem as a whole, but when you break it down into specific smaller problems, it gets much easier to reason about.

Websockets: real-time communication

Whenever we need low-latency, real-time communication in our apps, websockets are the more efficient choice.

Websockets are quite different from standard HTTP where the client asks for a piece of data and the server responds. The server has no way of initiating that communication, a request must come from the client.

With websockets we have bidirectional communication, meaning that both the client and the server can send messages to each other at any time, without waiting for a response from the other side.

Considering edge cases

Usually, when you're implementing a feature you're naturally drawn to only consider the golden path, because that is the way the app is intended to work. So you implement the feature and it's working without any issues.

However, since networking is involved there are a lot of side-effects that could occur in which we have no control over.

For example, I noticed an issue: I'd create a duel lobby and wait for someone to join, but if nobody showed and I closed the browser, that lobby stuck around forever. The next day I'd log back in and it was still there, which meant I couldn't create a fresh lobby, because the system thought my old one was still active.

It's crucial to consider all of the things that could possibly go wrong and implement fail-safes for those paths as well. You don't want your entire app to rely on a single thread of hope, and if it doesn't go according to that path everything breaks and falls apart.

Since websockets are stateful and persistent, we need to take a lot of edge cases into account and handle those gracefully.

What should happen when a user is in the middle of an ongoing duel and:

  • closes the browser?
  • browser tab crashes?
  • loses internet connection?

We need mechanisms for these unexpected events to clean up the state. So I implemented connect and disconnect handlers and made the design choice to keep the duel ongoing on the server even if a player disconnects or leaves.

I learned that you need to consider every edge case. It's best to anticipate them and have systems ready to handle things when they actually happen, so the app doesn't just break or crash. Better be prepared than sorry.

Drawing assets

This project didn't merely involve programming; design and drawing assets was part of it as well. I had to learn Adobe Illustrator in order to draw the item rewards for the chests.

Fun fact

I drew 118 chests and items in a low-poly style. Don't ask me how long that took.

The bigger picture is made up of small brushstrokes.

Theatre of blood chest
Lucky dragon chest
Corporeal beast chest

The uniqueness of this app made it very fun to build, it was nothing like I had ever done before and that came with its own challenges and learnings.

It brought back a lot of childhood memories from a game that I hold very dear.

I spent a lot of time on certain features of the app to get it just right, and at times it felt like I would never get it done.

But as a wise old man once told me:

If there is a will there is a way.