The Life of a Programmer

Dissecting my Rusty Superhero Bots — The code review

Β 

I chose Rust as the language for the Botters of the Galaxy contest. My code structure was suitable enough to get me to the final league, but I had a few design flaws. Some of the flaws were of my creation, and others resulted from uncertainty with the language. In this article I talk about my code, pointing out strengths and weaknesses, and looking for ways to improve for next time.

I’m using the profile EveningRust on the CodinGame site, which coincides my stream on Twitch. My final rank was 68th, out of 1713, coming in 2nd place for Rust.

Rust

I didn’t choose Rust solely for this competition, but rather for my stream “Evening Rust”. In these shows, I want to learn a new language and have some fun doing it. I started the challenges on CodinGame in January with zero knowledge about Rust.

I chose Rust as it seems to have an active community behind it and has exciting features. Understanding the features is important to me as I work on my language Leaf. Using a new language provides novel insights into language design.

rustThough still new to the language, I figured I knew enough to structure my code correctly. But there are still some significant hurdles I have to cross. My viewers have been fantastic in helping out: suggesting syntax and coding small examples.

If your goal is the top of the leaderboard, choosing an unfamiliar language is not wise. My goal was just to make it to the “Legendary League”, and not care about final position, but still, the time limits and pressure of the competition take their toll. In one stream, Day 2-2, I gave up in frustration: the language came down on me like a concrete roadblock.

State

The key structure to my bot was the game State: a Rust struct that wrapped up the entire state of the game. It was initialized from stdin, as per the game’s rules, and updated each turn.

State contained several lists and sub-structures, such as a them, us, and neutral Player type that stored information on the units, Unit, belonging to each player. The game didn’t provide me with such lists, only one large list of all units and the player they belonged to. I separated this into individual lists during the loading process.

pub struct State {
    pub map_entities: Vec,
    pub items: Vec,
    pub round_type: i32,
    pub us: Player,
    pub them: Player,
    pub neutral: Player,
}

A MapEntity stored map things, like, well, bushes. A list of items was also available for purchase, stored as the Item type. As this data came from stdin, it was all in text format. While loading, I converted it to appropriate types: integers for counters, floating point for positional and physical data, enums for enumerate values, arrays, et cetera.

The choice of structure impacts how the bot is programmed. At first, the chosen structure seemed fine.

Strategy and Turn and HeroDesc

Given this state, I needed to decide what action to take. I created a Turn type which encoded all possibilities for a turn.

pub enum Turn {
    Wait,
    Move(Point2),
    Attack(i32),
    AttackNearest(UnitType),
    MoveAttack(Point2,i32),
    Buy(String),
    Sell(String),
    SkillTarget(String, Point2),
    Skill(String),
    SkillUnit(String, i32),
}

Now I just needed something that could produce these.

I opted for a Strategy interface, called a Trait in Rust. I could create a different implementation of the strategy for different approaches. The first strategy was strat_cowardly — so-called as my heroes would run away at the hint of danger.

pub trait Strategy {
    fn get_turn(&self, state : &State) -> Turn;
}

I somehow needed to get the concrete abilities of each hero into the game: the basic stats were part of the Unit type, but they also had unique skills. I create a HeroDesc type that encoded this information. It was a bit unclear where this information should live. The strategy needed access to it, but it’s logically part of the Unit, it’s also referenced from the Player.

Complicating this here is how Rust limits the sharing of structures. I couldn’t merely link the units everywhere that was needed. I ended up using an ugly mess of indexes and lookups. As I progressed, it became wholly apparent that something was wrong here.

The idea of a generic Strategy also fell apart. I spawned strat_cowardly into strat_wimpy: a more advanced version. I also created a strat_smash which was a melee strategy. “Smash” quickly became a Hulk-only strategy though, as did “Wimpy” evolve into an Ironman-only strategy. The third hero I tried, Doctor Strange, immediately got it’s own strat_strange.

Given the time limits of the contest, I wasn’t excited about restructuring all my code. In retrospect, I probably should have. In the final days of the competition, I was hindered by this structure.

Some may say I overengineered my code for the competition. I was focused a lot on getting a good design as a way to further my Rust knowledge and allow my viewers to understand what was going on. The ten day time limit also gave plenty of time to do nearly proper coding, though I did skimp a lot on testing.

Mutating the State

There’s one last piece to this puzzle: mutating the state. I said that each turn the game presents us with updated state information. The game provided state isn’t good enough though — we need to track additional information as well as in-between states.

Each hero generated its own Turn, so all I needed was an apply_turn function on the State. I’d simply modify the state the way the game engine would when given the same turn. I didn’t have to implement everything, only the bits that were relevant to my strategy. For example, if I hit an enemy unit with my first hero, the second hero should see the new health. Or if I bought an item, I’d have to reduce the available gold, so the second hero doesn’t think it also has enough to buy an item.

pub fn apply_turn(&mut self, hero : usize, turn : &Turn)

Enter Rust stage left to accost me. It slapped me from the right, punched from the left, and kicked from behind. Just when I thought I was safe, it pushed me into the bushes and with a haughty voice demanded “Respect”! It was absolutely not cooperating with what I wanted to do.

It was writing apply_turn, in Day 2-2, when I cut off my stream early in frustration. I’m still trying to reserve my opinions of Rust until I know it better; I needed to prevent my normal shots at Rust from growing into a wild tirade.

You see, Rust has this borrow checker which enforces absolute memory safety. That sounds good until I realize that virtually every technique I’ve ever learned violates the requirements of this model. Wrapping my head around this paradigm is still an issue.

We’re left with functioning monstrosities in the apply_turn code. My goal for Rust will be to learn how to avoid this ugliness. The Rust community has been helpful, but the short answer so far is still a big question mark.

Next Time

My biggest challenge for the next contest is designing a State that is both useful and cooperates with Rust’s lifetime and borrowing rules. I’m also unfamiliar with how I produce an abstract base and virtual function pattern. I wanted that from Day 1 but chose a different structure instead. I’ve not yet unravelled the paradigm Rust is preferring.

From the logic standpoint, I need to incorporate longer lived decisions; not just turn based. In the first article I talk about how this limited me. It’d require each hero unit, of mine, have a current goal. Instead of the strategy just producing a Turn, it’d instead produce a Goal. Separate logic would then create turns until that Goal was fulfilled, or a better goal came up.

Overall, given the game scope and my experience, I’m satisfied with my code. A position in top 0.5%, and second place for Rust, definitely means I did a lot more right than wrong here. There is room for improvement next time, but I walk away from this contest a happy person.

Please join me on Discord to discuss, or ping me on Mastadon.

Dissecting my Rusty Superhero Bots — The code review

A Harmony of People. Code That Runs the World. And the Individual Behind the Keyboard.

Mailing List

Signup to my mailing list to get notified of each article I publish.

Recent Posts