Init 1

Dogfight!

posted on 2024 Dec 29

A couple of weeks ago, inspired by an as gripping as nerdy tv series (check out Halt and Catch Fire 🔥), I explored a little idea, a small, self-contained low level project to build an old arcade game - battletank arena - with a little kink in the form of a centralized TCP server to handle multiplayer through the network.

The rules were simple

  • Keep it small and simple to keep it fun
  • Use the least amount of external dependencies as possible

I think I somewhat respected them as much as possible, not pedantically but in a good measure. During the development I

  • Developed a first simpler version using ncurses to handle the “GUI”
  • Implemented some very dumb synchronization logic to keep FPS consistent between players
  • An even dumber binary protocol to communicate, game state being the only payload exchanged
  • A micro set of features (bullets, power ups, life points etc)

I then moved on and got intrigued by the possibility of extending the client side of the game with a little more ambitious graphical layer, using raylib. The library is pretty solid, small but powerful; I still kept most of the core feature-set as simple as possible, in no particular order:

  • Select server based with non-blocking sockets, no async libraries such as UV and the like
  • The binary protocol exchanges the entire game state, while it’s fine for the size is pretty contained, it would’ve been simple to evolve it to send delta updates
  • All the controls are keyboard based, no oblique movements, acceleration
  • No scaling/proper handling of the screen size of each client (nasty bugs are totally ignored)
  • No optimizations of sort, not lag compensation, path prediction and other painful network management issues
  • No sprite animations, bullet explosions and so on

All of the above are “easy” upgrades that could be tackled, what caught my curiosity is, on paper, this kind of games are mostly heavily I/O based, the heavy work is made on client side to render something eye-pleasing essentially, how well would it work an highly scalable TCP server in another language instead of C to manage the multiplayer stack, e.g. games, rooms, chats possibly and so on. So I decided to give it a go in Elixir and port my battletank-server to an elixir based implementation, which would make it painless to distribute in a cluster and happily use all the resources of all the nodes.

Goals

Once again, simplicity and a small codebase are paramount here, the project aims to be a variations on the previous iteration, hopefully with some additions enabled by how convenient elixir is for network-based soft real-time applications. There is no expectations or quality goals, it is intended to be an exploratory and fun work.

Moreover, the development will be divided in the server side and the client side, leveraging most of the work previously done.

Server side

  • An elixir based TCP server, probably using the builtin :gen_tcp from erlang
  • Binary encoding/decoding of the game state, subsequently add delta updates as suggested above
  • Distributed by default, probably throwing libcluster and horde to the mix to make it smoother
  • Game lobbies, chats, maybe a leaderboard

Client side

  • Adding support to delta encoding/decoding of the game state
  • Explore further raylib, nicer sprites and possibly animations
  • A menu or something to enable a game lobby or hosting a game
  • In-game chat support
  • Possibly written in Zig. Although I can already leverage a C implementation it may be a good opportunity to explore a new language, plus Zig has full ABI compatibility with C and a nice Raylib port

I reckon all of the above, although it doesn’t look like that much, is pretty ambitious and will take some time to be achieved. There will be bugs.

Roadmap

Coding will happen during free-time and will not necessarily be consistent, I will try to journal as much as possible, but the high-level roadmap is expected to be something like

  • Server - basic TCP server setup
  • Server - add support for the binary protocol used to communicate in battletank
  • Server - add support for game instantiation and distribution across multiple nodes
  • Client - some graphical improvements
  • Both - delta encoding/decoding of the game state
  • Both - extending the protocol to control pre-game logic (lobbies, game hosting etc)
  • Both - cherries on top, refinements

I’ll try to stick to it by documenting this little journey in short articles, with the following currently available in various stage of development (some are to be considered stretch goals)

  • Dogfight! - Elixir server
  • Dogfight! - C and Elixir, testing the server
  • Dogfight! - Zig, rewriting the client
  • Dogfight! - Zig, checking Raylib.zig
  • Dogfight! - Elixir and Zig, game state revamp
  • Dogfight! - Elixir and Zig, delta state encoding
  • Dogfight! - Elixir and Zig, hosting a game
  • Dogfight! - Elixir and Zig, in game chat

References