kartoffels v0.6 released!

kartoffels is a game where you're given a potato and your job is to implement a firmware for it:

https://kartoffels.pwy.io or ssh kartoffels.pwy.io | source code

Today I'm releasing v0.6, which spans 120 commits and brings:

New challenges

kartoffels strives to be both a multiplayer and a singleplayer game - to achieve that, the game provides challenges where you're tasked with implementing a robot that solves a specific problem, kinda like Advent of Code or Project Euler.

This version brings two new challenges, diamond-heist and personal-roomba:

I think personal-roomba is my favorite challenge so far - you're thrown into a randomly generated maze (with cycles!) and your job is to implement a robot that scans the territory and collects all four flags located at the maze's corners:

It's a perfect opportunity to play with Dijkstra, CPU and memory optimizations, and what have you.

Simplified radar functions

Radar allows for a robot to scan its surrounding area, making it a critical component to get right for robot to work. In the previous version the radar_scan_* functions returned a 2d array containing the entire scan:

pub fn radar_scan_3x3() -> [[char; 3]; 3];

This design proved a bit awkward to use due to its untypical indexing:

let scan = radar_scan_3x3();

// e.g.:
// scan = [
//     ['.', '.', '.'],
//     ['.', '@', '.'],
//     ['.', '.', '.'],
// ]
//
// ... which gives us:
//
// scan[1][1] -> center of the scan (i.e. the robot iself)
// scan[0][1] -> tile in front of the robot
// scan[2][1] -> tile behind the robot
// scan[1][0] -> tile to the left of the robot
// scan[1][2] -> tile to the right of the robot

... and so the radar_scan_* functions were redesigned to return a struct with a convenient accessor:

let scan = radar_scan_3x3();

// scan.at(0, 0)  -> center of the scan (i.e. the robot iself)
// scan.at(0, -1) -> tile in front of the robot
// scan.at(0, 1)  -> tile behind the robot
// scan.at(-1, 0) -> tile to the left of the robot
// scan.at(1, 0)  -> tile to the right of the robot

Seizing the day, the radar got extra data - you can now use it to check which specific robot is at given location:

if let Some(bot_id) = scan.bot_at(0, -1) {
    if !friends.contains(&bot_id) {
        arm_stab();
    }
}

arm_pick() and arm_drop()

Robots can now pick objects from the ground and drop them.

A diamond, held captive by four guards.

Currently this comes handy only for the two new challenges, since objects are not spawned in the multiplayer mode (and robots can't create objects out of thin air themselves).

I'm thinking of using this feature to introduce a new game mode, capture the flag.

Em Dashes

A friend said it's illegal to use - when I really mean - I've read a bit about it, got anxious, drank coffee, got more anxious, and went through all of the texts to s/-/— where applicable:

An em dash, held captive by letters and spaces.

I'm still a bit on the fence on this, mostly because writing is kinda annoying, but I see the merit.

inspect-bot command

You can now inspect the history of each bot:

Currently the times are always displayed in UTC+0 and you can't search through the history, but it still comes handy e.g. to see why your bot is dead (it might've gotten killed, the firmware might've crashed etc.).

Smooth camera movements

Camera now interpolates between current and target position, making for a smoother experience:

Compression

As it turns out, a text interface can generate a lot of data!

This issue stems mostly from ANSI escape codes, used by kartoffels (and so indirectly via Ratatui) to render colorful text, change background colors etc.

For instance, if you'd like for the terminal to print something using an arbitrary RGB color of, say, rgb(123, 250, 33), you'd have to send \x1b[38;2;123;250;33m - that's 18 bytes just to change the color!

(\x1b is a single byte with hex code 0x1b - it's a special "character" that informs the terminal that the following bytes encode an instruction such as pls change foreground color)

For instance, when the camera is moving, a 125x60 x 30 FPS session generates about 0.8 MB/s of traffic. It's nothing for an application running locally, but sending 0.8 MB/s over the internet for each session would quickly add up on my Hetzner VPS.

To remedy the issue, the backend - both for SSH and WebSockets connections - now compresses the frames using the flate2 crate before sending them out. The CPU impact is minimal, while the network traffic is greatly reduced.

Plans

For the next release, I'm planning to continue implementing more challenges - I've also got !36 on my list, gotta give more love to the multiplayer mode.

If you have any ideas yourself, I'd love to hear them!

The v0.7 release will most likely happen some time next year, since I've gotta focus on a couple of other things that have piled up that are important to me as well.

Anyway, come and play! (or ssh kartoffels.pwy.io)