It All Started With a Gift
Upon thinking what to gift to my dad on this year’s father’s day, I highly considered gifting a board game. He loves them! One of the standout games he liked were Carcassonne, Ticket to Ride and Cascadia, as well as chess. I started searching for something physical and stumbled upon a game called Linja. One of the key reasons I chose this game specifically was because it’s not hard to explain, and has a very simple design.
After that, I tried looking into whether there was an online adaptation of this game, and I was surprised to find out that there wasn’t one. Every result that popped up on Google was not even about Linja. Google was mistaking the name for the query Ninja, which is very funny, because Linja is not related to ninjas.
Filled with excitement, I decided to make an internet version myself. By blending words Linja and online, I came up with the name Onlinja.
Picking the Tools
I knew from the start that I wanted to learn something while creating Onlinja. I was very bad at JavaScript, so I decided to break the ice and learn React and its twin React Native.
Another very fascinating thing for me was the idea of how game bots worked. The only game bots I have developed were simple characters that chased the player in 3D space by rotating and moving towards the player. I wanted to learn how to make a bot that could play a game at different levels of difficulty and replicate human behaviour.
Frontend Development
The reason I picked React Native was because I always chased platform-independence with a single codebase. It fit perfectly. In my previous attempts of something similar I tried Flutter but wasn’t too satisfied.
My goal initially was to also make a server that could handle crossplay, but I quickly realized that I didn’t have enough time to do that, as I needed to showcase the project as soon as possible. This plan isn’t abandoned, but the current version of Onlinja is an offline front-end experience.
Stage 1: Prototyping And Designing
I was always aiming for a minimalistic design to not escape the original game’s identity. So I picked a very clean monochrome color scheme and rounded corners for elements.
One of the hardest things was to figure out how to implement a game board. Seems simple at first, but to make sure moves are valid, I had to code helper functions that decided whether they were. I won’t lean too much into the game’s rules, but the core framework is:
- You select a piece: You pick a piece to move.
- You move a piece: You move your selected piece one lane forward.
- You select another piece: You pick another piece to move.
- You move the piece: You must advance your second piece as many lanes forward as there were pieces in the lane you landed your first piece on.
The whole ruleset slightly differs from the original and can be a bit difficult to grasp. The game desperately needed a tutorial. It was actually one of the last things I implemented.
Stage 2: Action!
After making a main menu and a game board, only thing you could do was play against yourself (or your friend on the same device). A bot was necessary to make the game entertaining and I started working on it. The key things I learned were:
- Math: Game bot development is pure math and chance calculation.
- Assumptions: Game bots frequently assume you’ll make the best move possible, and try to defend from it.
- Lookahead: To make a game bot think into the future, lookahead depth is required. And one of the best ways to quickly make a difficulty system is to vary the depth. The more depth there is, the more difficult the bot is to beat.
- Randomness: To make a bot feel more human, randomness is required. A bot can’t always pick the best move to play, even if the calculation results in such.
- Minimax: This is a common algorithm used in game bot development. It calculates the best move by minimizing the possible loss for a worst-case scenario. This algorithm is very useful, but expands in time complexity rapidly.
- Advanced difficulty: By introducing a separate scoring system for the bot, you can punish the bot with a lower score for making a move that is bad for the long run, even if it currently looks okay. Making perfect move chances lower also helps when designing difficulty levels.
After a few days of research, I dived into the implementation. Interesting or not, the bot was updated constantly throughout the development of Onlinja due to how real I wanted to make it feel. Furthermore, as with all software, there were bugs - the bot would get stuck, apply difficulty incorrectly, or even make invalid moves.
Stage 3: User Experience
After hopping off my PC for a moment, I realized how hard is it to keep track of the game state. I remembered how Chess.com allowed you to see last move made, and I decided to implement a similar thing. This made Onlinja way more enjoyable.
Also, the animations were janky, and I had to think of something better. I used a library called Moti with custom bezier curves for a smooth feel.
Stage 4: Developer Experience
When developing, I found that some things were unintentionally hardcoded. I forgot to modularize them. However, by creating a clean architecture for development, I had to give up some user-end conveniences for the sake of maintainability.
One of the things was a new tab system. And as much as I like how it functions, since it’s wrapped now, the elements can’t always escape an invisible bounding box. Some visuals were cut.
Another thing I didn’t make a good call with was the early switch from React to React Native. Even though I love the ability to build native apps, designing correct spacing and layout for mobile devices introduces incompatibility with web browsers. Onlinja looks great on the web, even on mobile browsers, because it was made for it. But my efforts to roll back to a web-only stack were too late. I decided to just keep React Native. Luckily enough, very late in development I managed to reimplement layout handling and it works great now.
Stage 5: The Tutorial
I think the tutorial is one of the most important parts of Onlinja. And it came out great! I can easily customize it through a single config file, and it’s easy to follow, even on some of the hardest game concepts.
With everything in place, I’m calling Onlinja a finished project.
Community Feedback
Community also helped me to finish shaping Onlinja. Turns out, things I had planned after the initial release were truly unnecessary. Game received positive reviews on this year’s Hack Club’s Stardance challenge, and the contrast between what community wanted and what I wanted was very clear. Reconsidering my choices I realized that the community was right on things like:
- Turn indication: For me it was clear whose turn was it. For players? Not so much. I added a turn indicator overlay.
- Tutorial save/resume: A great idea I though was unnecessary at first. Now you can resume the tutorial from where you left off.
Things I added as extra quality of life features were:
- Post-game analysis: After a game is finished, you can see simple game statistics.
- Settings: You can change your default preferences and they get saved for the next time you play. This includes starting side, difficulty level, lane highlighting and more.
Conclusion
Onlinja not only provided me with the joy of coding, but also users the joy of playing. I hope to see more people play it. Making a multiplayer version in the future would be a dream come true. I’m happy with my efforts of making a game out of the love for another and hope to see more projects like this emerge all across the world.
Make sure to check out Onlinja at Onlinja’s official page!