Accidental evolution


This game, Martian Dice for Pico-8, evolved entirely by accident. More than anything, it's the end result of an unplanned journey of techological exploration.

Optimal Play

It started when I was introduced to the game Martian Dice on boardgame arena by a friend. It's quite a simple game, but not so simple that it's clear how to play the game optimally. I was curious what the optimal play was, and how much points a perfect player could expect per turn. So I decided to program this logic in Python. As it turns out, the expected score when playing optimally is 3.11. Not that much really.

The below table shows how Optimal play compares to other, much more basic play strategies in a two-player game. The probabilities are not symmetric, as the first player (the rows) has an advantage.

RandomAggressiveDefensiveOptimal
Random52%47%15%6%
Aggressive57%52%26%15%
Defensive89%78%55%27%
Optimal96%89%
80%56%

Cheap, serverless Web Sockets

This project is my first ever online multi-player game. To prepare for upcoming game jams, I wanted to explore some technical possibilities in this area. In particular, I wanted to see how well Web Sockets would work in an AWS-hosted serverless backend. I was interested in serverless, as this can be a very cost-effective way to host low-traffic services in the cloud, and AWS happens to be the stack that I have experience with professionally.

Web Sockets in serverless computing are not a natural fit. Serverless services are short-lived and stateless whereas Web Sockets are longer lived and typically require shared state across a group of connections. So I was curious how this would work in practise. When exploring a new technology, it helps to pick something simple. So it was natural to continue with Martian Dice. Its turn-based nature also meant that performance would not be an issue.


There's server-side state required to let all clients in a room communicate. When one client makes a move or sends a chat, the server needs to know who the other clients in the room are that this update should be broadcasted to. For storing state I initially used memcached, but that requires a VPC in AWS. Furthermore, the memcached node is continuously running, which is a bit of an overkill for a low-traffic game. I therefore refactored the code to use DynamoDb instead. This is slightly slower than an in-memory cache. However, for this game that would not be an issue.

Client-side game state maintenance

Initially I also stored the game state in DynamoDb. However, that requires relatively many database writes (one for every move). For a four player game, lasting eight rounds, with on average three turns, this meant approximately 4 * 8 * 3 * 1.5 = 144 writes per game, which I considered a bit much. So I refactored this to only let the clients retain the game state. When making a move, the client passes the current game state along to the backend.

You may now wonder, does that not open the door for malicious clients to cheat? No, not really. The state updates are still all handled by the backend. The backend also throws the dice, updates scores, and enforces all game rules. As the backend does not store game state, this needs to be sent by each client whenever it makes a move. So a malicious client could in principle pass an modified state. However, each state also includes a hash-based checksum. When the backend receives a move, it verifies the checksum of the incoming state. If it's incorrect, it will ignore it. So when a malicious client patches the game state, it also needs to update the checksum. However, when broadcasting the new game state, the server does not only include checksum of this new state, but also that of the state from which it was derived. This enables the other clients to detect a cheating client in their midst.

React-based client

The PICO-8 client hosted here on Itch.io was not the first client I wrote. First I created a React-based client, as I wanted to gain some experience with ReactJS. The screenshot below gives an idea what that looked like. It uses more screen real-estate than the later PICO-8 client. The dice also have a much higher resolution, but were quite simple already.

PICO-8 Client

Next, I thought it would be interesting to port it to PICO-8. First, I wanted to see how well the game could be ported to PICO-8's 128x128 resolution. This did not leave much room for in-game chat but I am happy with the result.

The PICO-8 client cannot directly communicate with the backend. To enable this, the PICO-8 GPIO interface had to be used. GPIO is used to communicate with custom Javascript code that handles communication with the backend. This is not rocket science, but relatively cumbersome to implement. In particular, it means you cannot test from inside PICO-8 directly. Every time you want to test, you need to export to HTML and test together with the client-side Javascript code, which in turn communicates with a (locally running) backend.


Conclusion

If you got this far, you'll have seen that there was no pre-conceived plan to develop this game. It really was a sequence of technology explorations. With that in mind, I am happy with the result. It's a pretty basic game, but hopefully brings some fun. Challenge your friends!

Finally, for those interested, all source code can be found on github.

Leave a comment

Log in with itch.io to leave a comment.