Julien Jorge's Personal Website

Version 17 of Bim! with nicknames and a lot of technical detail

Mon Oct 19, 2026

It’s been a long time since the last article I did about Bim! and I was indeed on a quite massive part during the last months. But now it’s done, I think I have finished my thing and I have published version 17 not long ago. Cool!

As a reminder, Bim! is a free and libre open-source game of the last-man-standing arcade genre (AGPL3 code and CC-by-sa 4.0 assets). It runs only on Android and the games are exclusively online.

It is available on the PlayStore, and also on GitHub, F-Droid, and some alternative stores.

The game looks like this:

This new version is mostly technical. The visible changes for the user are as follows:

  • The player has a nickname now. It is displayed on the matchmaking screen.
  • The duration of the temporary invincibility when losing a shield is shorter.
  • The players who lose before the end of a draw game are considered as losers (in prior versions of the game they were included in the tie).

This is not much, so why was it so long?

Introducing the business server

The list above contains a small thing that convinced me more deeply about the need of a business server to handle the player’s inventory, the client configuration, the available game servers, and so on. It was the player’s nickname.

Historically Bim! had only two main parts: the client and the game server; and on the side of them was sitting a JSON file on a web server, that the client had to download on launch to get some configuration parameters for the app, and also to get the list of the game servers. The player’s state was on the client (coin balance, acquired and selected game features, etc.) The game server’s job was to match the clients and to tell them how many coins they won when the fights were over.

It was unpleasant on many dimensions. There’s obviously the risk of players cheating when the data is on the client but with my small player base I doubt that to be of significant importance. However, having to maintain the config file by hand on the web server, this was hacking me off. I always worry of introducing a typo and I was frequently wondering if I had uploaded the correct file. Moreover, conceptually it was smelling bad.

Add to this that many features considered for the future of the game would be quite difficult to implement without a business server. For example, I would like to provide private rooms for players to play against each other rather than going through the global matchmaking. It would be hard to do that without a persistence on a server. If we also want the player to the rooms, and them to be shareable, it gets even more complex. Same if I want a leader board, I won’t store it on the client.

The nickname could have been on the client; it would have been sent to the game server which would have forwarded it to the other players. But it’s a bit of a hack.

I have also considered to have everything managed by the game server but it does not hold water. Firstly, the game server has no persistence, when it restarts it has forgotten everything about the last run. Then, there are frequently many game servers running: one for the last version, one for the version before; sometimes even one for the next version. Thus I would have had to use a storage shared by all game servers, which would have been almost like having a business server.

In short, everything converges toward the necessity of a business server.

Remains the question of which tools I would use. Since Bim! is written in C++ it is the first solution I thought of. On the pros side, I know this language quite well thus it would be easy to get started. On the cons side, when one talks about a business one talks about web services and databases, and when one talks about web services and database one rarely think about C++. The projects covering my needs are quite niche, it strongly smell like a difficult path.

By the way, since we are talking about difficulties, I have to point out that web development, on the front end or the back end, is not my domain of expertise, and that I did not work with databases since I left college more than 20 years ago. This business server story is thus an adventure for me.

The second choice would be based on Java, certainly using Spring Boot. I know this language, I think I can manage even if I like not to use it, and I worked with a great developer ten years ago who solved the same problem with those tools. I could get some insights by doing code review for him and I know that it fits the bill.

The third choice is to do something in Rust. I wanted to try this language for a long time and I love the idea of avoiding many vulnerabilities on my server. This is thus the tool I chose. Unfortunately I know nothing about programming in Rust…

Business server architecture

Like any modern developer worth their weight in gold facing a technology they know nothing about, I opted for the single valuable approach. I asked an LLM to generate the application for me.

Hey! I know nothing, thus it’s better than what I would have done!

Just kidding, of course :) I would not have missed this opportunity to learn. Thus I dive into a beginner book about Rust. A book that may seem a bit light to readers having decades of software development behind them, but on the other hand, if I get back in the mind of teenage-me, I think it is as good as the books I read to learn Pascal. It allowed me to get started, copying and compiling small programs even though I did not understand everything, poking lines to see what happens… Well, I was discovering and learning.

I developed a small proof of concept and asked people on the web for criticism so I could began the main project in a good state. One person did came to tweak the project and teach me a couple of things to get started. Many thanks to him!

In the end this is what we have on the business server:

  • a PostgreSQL database for persistence,
  • business code as an interface to the database using tokio-postgres (more precisely, using deadpool-postgres, which will handle a pool of connections to the database),
  • a web service based on axum which will get the info from the business module.

Once all those parts were set all I had to do was to describe the tables, to write the business code, the web services… I had no idea of the size of it when I began, it uncovered as I was moving the properties from the client and game server to the business server. At least, what was initially on the client seemed quite small. The first milestone was to send the configuration to the client, with the list of game servers, the content of the shop, and the price of the game features.

I start small with the game server. First I need a way for it to register itself to the business when it comes online. Wait, to do so I also need a way to allow those servers to announce themselves, in order to avoid rogue servers to register. Well, thus I need some admin interface… And a clean-up procedure to remove servers for which I have no news. This small part is not so small.

Then comes the shop on the client. Now I need a concept of shop and an admin interface to add, to edit, to list, to remove entries from the shop.

The game features follow (fog of war, fences…). I need a table to list them, with their prices. As well as the price for the additional slot.

I must also remember the inventory of the players, and certainly keep a transaction log.

By the way, what is a “player”? Well, it seems that I need a solution to identify the players, and also a concept of sessions, and to have the game server to validate the sessions with the business server in order for them to stay in sync when rewards have to be given to the players.

Oh there’s that too. The game server must inform the business when a game begins, when it ends, and what was the result, such that the latter can update the players’ stats and give them their rewards.

A short side note, if you wonder how the business identifies the player, it’s an UUID generated on the client when the app is launched for the first time. It’s more an install ID rather than a player ID. This UUID exists solely on the client and in a table on the business server. End note.

Well, when everything is put together I have 25 tables and 30 URLs.

Then it had a name

As I was reaching the end of all of this, I implemented a way for the players to chose a nickname; the thing that got me into the implementation of the business server in the first place. What a recipe for problems to allow the players to edit their nickname!

There is obviously the risk of the user submitting garbage. We are on the Internet, I know you, you are going to enter awful things while thinking you’re clever. I have thus implemented a way for the admin to easily change the nicknames. I have also set a delay between two nickname changes to avoid players entering something insulting (as an example) for a short time such that I could not see the problem.

Then there is the question of the nickname’s length. I can’t allow unbounded inputs, otherwise it becomes a storage service (and it will break everything), thus I have set an arbitrary maximum length. There is also a minimal length but I’m not sure about it. This brought be of course on the topic of the count of characters in Unicode strings, which I cowardly delegated to a library because enough with all of this.

And then there is the topic of displaying the nickname.

It hit me when I entered the first nickname containing an emoji. The the game engine I use the labels displaying text are configured with a single font, embedded in the application’s package (i.e. they don’t use the system fonts). What happens if the font does not contain the glyph? Nothing. It displays nothing. Argh what should I do if the user begins to enter things I did not plan? Emojis, Asiatic or Arab symbols… I cannot embed all the fonts, it would be unsustainable, and dumb.

I said that the engine does not use system fonts but it’s not exactly true. There is a label kind that does use the system, but then I have no outlines on Linux, and the algorithm to automatically reduce the font size to avoid overflows becomes unavailable. not great.

Seems like a dead end. Either I give up, or I roll up my sleeves. Here begins the surprise development as I thought I was in the home stretch: I patch the engine such that it can handle many fonts in a single label. A huge task on a key class that impacts everything. There are many kind of labels, with bitmap fonts the old-school way, with FreeType, with tons of effects to take into account (outline, shadow, underscore, glow…), and shaders to manage. As I find my way into this I discover that the emoji font I use (Noto) has colored bitmap glyphs, however the labels in the engine are designed for monochrome vector glyphs. Complete mismatch. Thus I add code to handle colored bitmap TTF fonts (everything goes through FreeType). Finally I also add a solution to pick glyphs in additional fonts and in the system’s fonts when a glyph is missing for a label. This is not obvious because the function has to find the font files containing the glyph, but also in a style close to the initial one (e.g. bold if the main font is bold). There are tools to find fonts by similarity on varied characteristics; I used Fontconfig on Linux. Android has an API starting with version 29 but this is a bit too high for my target (23) and does not cover my needs, thus I have implemented an ad-hoc solution.

I learned some things on the way. Did you know that TTF fonts could contain glyphs in SVG format? There is a variant of the Noto Emoji font that uses this. I have not implemented support for it in the engine because it was already complex enough. Actually there are so many things in TTF that I wonder if it is not Turing-complete.

Another discovery, in the availability of GNU Unifont. This is a monochrome bitmap font with glyphs of 8×16 or 16×16 pixels covering the entirety of Unicode. This is simple, small, and cute.

After all this work in the engine I have sent my patch upstream. I hesitated quite a lot because the last time I tried to contribute to this project it was a very unpleasant experience. On the other hand I think that contributing upstream is The Right Thing To Do™, and I don’t want to maintain such a huge patch on my fork. I have thus created a pull request and it was well received. It’s not merged yet but the contribution is welcome.

A few words about the tests

There is not many algorithms nor data structures on the server except for some convenience code. I have few things to test except checking that each URL does what it has been asked. As a result my tests are mainly a bunch of runs of the curl program. The nice part is that those tests are as-if the requests came from the client; it’s reassuring. In the general use-case I have an end-point allowing to check the result, otherwise I do some requests directly in the database. It was very useful during the development. As usual, I hate writing tests but I’m very happy to have them when I do huge transformations in the main code.

The most painful part in all of this is that I have to launch a Bim! business server and a PostgreSQL server. And it’s even worse when testing the client. I used to only have to start a game server for the tests, now I also have to launch the business and to wait for the game server to announce itself to it. A bit of a chore but thankfully I could script everything.

On the client’s side it’s a bit complex to test all requests toward the server, consequently I have mock-ups and I mostly test the serialization of sent data, and hypothetically received data. I also have UI tests going through all screens, and with this I think I cover a good share of the user experience. The only part I skipped in the automated tests is the push of the legacy inventory from the client, by laziness. Now guess which part crashed when I ran the manual tests? Indeed… And it was hell to find the root cause. I should have written more tests.

A few words about Rust.

I’m going to take advantage of this post to give some feedback about Rust as a newbie. There are great things, and less great things, but I guess that what I dislike is mainly due to the fact that I’m a newcomer who knows nothing about the tool.

The famous borrow-checker is really great. It’s a pain to satisfy but one sees quickly how useful it is.

Error handling seems far too fine for me. Most of the errors are not handled and just forwarded to the call site. It causes a lot of code and transformations only for the errors to finish in an existence test like « is there an error? ». Not really useful. I believe exceptions would have been a great solution here but there is none in Rust. It’s unfortunate because after decades of hearing the industry repeating that exceptions are inefficient someone actually checked and discovered that it was actually great. In addition to better performance it causes lighter interfaces. (Yes I know that exceptions are still bad for readability and that it hides the issues :))

Not directly related to Rust but to the tools I used imply asynchronous programming using tokio. Consequently I have awaits everywhere, it’s a bit onerous. I would have preferred a solution with parallelism at a higher level. If you add to this the question marks to forward errors, it.await? becomes.await? laughable.await?

The if let Some(x) = y pattern is really pleasant and I was quite surprised to see myself reworking functions to use it rather than calls to unwrap().

Too many calls for help in Rust forums get degrading answers in the like of “this is not what you want to do” or the classical “today’s computers are powerful”. It feels like going back years behind. When someone asks you how to do something, tell them how, and only then you can explain why it’s a inefficient way to tackle their problem.

The build duration is rather long and the inability to compile files independently is a handicap. Moreover Rust is on par with C++ regarding the verbosity of error messages, and if a Trait is ever misplaced debugging it is hell even with the compiler’s hints.

I’m reserved about Cargo who downloads the whole Internet to get the dependencies, and also about this trend to create tiny libraries for everything. On the other hand it seems well implemented and more robust than anything we can find one NodeJS’ side, but don’t we expose ourselves to a lot of junk and issues by pulling dependency trees like this?

That’s all I have as a newcomer. Maybe next time, with a longer experience, I will have changed my mind and will tell the opposite :)

The code

By the way, the code is here. This is AGPL3. I hesitated about putting everything in the repository of Bim! because it would be easier for the tests, however the independent repository forces me to think the business server as an autonomous project. I believe it to be better on the software design dimension but I do not forbid myself to merge the projects in the future.

Some stats

As usual we are going to take stock of the situation with some statistics. The previous version introduced bots such that the players could play even if the game server could not find an opponent for them. Did it change anything on the activity?

If we check the count of games per hour, there is a clear increase compared to the previous version, with peaks regularly above 30.

On the sessions’ side there are fewer than 20 per hour, except in the first days of September where there was a huge peak for an unknown reason, with seemingly no game launched. The logs say it comes from the US but I did not dig deeper.

In terms of player count per game, by aggregating the count of games since version 16, on the 20th of April, until today (almost 6 months) we get something like this:

Player count Game count
1 51508
2 4049
3 330
4 34

Those figures are simultaneously great and deceiving. Great because it’s a lot of games, but deceiving because the count of games with human players only is proportionally lower than what we had when publishing in version 16. At the time, aggregated over three months, it was 4150 two-players games, 916 three-players games, and 293 with four players. I don’t know how to interpret that. I guess this is a great score anyway and that if there are more players they will mechanically match with each other.

Finally we get to the country distribution:

And the same data seen by PostHog, not identical but close:

That will be all for this very technical post. We’ll meet again for the next version with, I hope, more features visible for the users :)