DEV Community

Cover image for My FCAOOC "Asteroids" Experience
Matteo Ruggiero
Matteo Ruggiero

Posted on

My FCAOOC "Asteroids" Experience

In this post I'll walk trough my personal experience with this amazing competition.

First of all, what's FCAOOC?

Fast, Cheap & Out of Control is a series of educational challenges focused on the Stellar network’s smart contract platform, Soroban.
Every challenge will be different, but the theme is fairly oriented to retro-games and stuff as the website style and the first challenge title may suggest

How did I approach "Asteroids"

Every challenge has 3 different bucket:
-Fast, oriented at completing the quest in as less time as possible after launch;
-Cheap, oriented at completing the quest with fewest CPU instruction used;
-Out of Control, and this one is really out of control, oriented at completing the quest with the smallest compiled contract size.

First day (it was night, 1AM or so) I decided to take a look at the challenge "How to Play" section, wrote some notes and started drafting a solution on paper.
After some well deserved sleep and no ideas I tought of writing my solution in the test.rs file, kinda "mapping" galaxy by galaxy a premade path and testing step by step if it was working as expected: "well, let's grab some crayons and squared sheets!"
After a lot of drawing my "Fast" solution was done, a 421 lines code that just describes the path as a sequence of moves, turns, shoot and mad refueling!
Got 13th place for Fast, 1st for Cheap and 7th for Out of Control...

But it was just day 2, other Lumenauts will surpass him in no time

After some boring Uni stuff and encouraged by the USDC prize I decided to work on a better solution for the Out of Control bucket.
"Smallest size, I need less lines of code, some kind of logic, a premade path won't work on this..."
So, in order to craft my OOC solution I actually had to think instead of drawing 🤣🤣🤣

#![no_std]
use engine::{MapElement,Direction};
use soroban_sdk::{contractimpl, BytesN, Env};

pub struct Solution;

mod engine {
    soroban_sdk::contractimport!(file = "../game_engine.wasm");
}

mod test;

#[contractimpl]
impl Solution {
    pub fn solve(env: Env, engine_id: BytesN<32>) {
        let engine = engine::Client::new(&env, &engine_id);

        let mut upgraded  = 0;
        while engine.p_points()<100{

            let map = engine.get_map();


            if upgraded == 0 && engine.p_points() >= 5   {
                engine.p_upgrade();
                upgraded = 1;
            } 

            for elemento in map.iter(){
                let (key,val) = elemento.unwrap();

                let  x = key.0;
                let  y = key.1;

                if (val == MapElement::Asteroid && engine.p_fuel() > 100) || (val == MapElement::FuelPod && engine.p_fuel() < 100) {
                    while engine.p_pos().0 != x && engine.p_pos().1  != y {
                        let position = engine.p_pos();

                        let dx = position.0 - x;
                        let dy = position.1 - y;

                        if dx < 0 {  
                            engine.p_turn(&Direction::Right);
                            engine.p_move(&Some(dx.abs() as u32));
                        }
                        else {
                            engine.p_turn(&Direction::Left);
                            engine.p_move(&Some(dx as u32));
                        }

                        if dy < 0 {  
                            engine.p_turn(&Direction::Up);
                            engine.p_move(&Some(dy.abs() as u32));
                        }
                        else {
                            engine.p_turn(&Direction::Down);
                            engine.p_move(&Some(dy as u32));
                        } 
                    }
                    if val == MapElement::Asteroid 
                    {engine.p_shoot();
                    }
                    else {engine.p_harvest();
                    }
                }
        }
        //sto fuori al for each element

        engine.p_turn(&Direction::Down);
        engine.p_move(&Some(8));

    } 
 }
}
Enter fullscreen mode Exit fullscreen mode

The solution I ended submitting was a little bit different (removed something, adjusted something else 👀) but this works as an example: while my points are less than the required for completing the challenge scan the map, store that and for each element in that map if it's an asteroid and you have a lot of fuel reach it and shoot, if it's a fuelpod and you have a lot of fuel but maybe not that much reach it and harvest.
After emptying a galaxy just move some spaces down and hope you're in a new galaxy 🤣.
After compiling that solution with nightly rust and optimizing with soroban contract optimize I got a score of 1.543 and an honest 33rd final place.

A "cheap" decline

3 days till the end of the competition - "I must try to improve my Cheap score, or else I won't be in top 100!" - and that was my motivation, now on to the strategy: improve my premade path solution that originally was the cheapest, probably people are doing the same thing as me but better.
My Fast solution was made by manually mapping and drawing galaxies and only going right, i tried looking at where i could save some moves, double shots, useless turns and stuff like that and improved the path, submitted the new ship and got a better score but I was quite not satisfied yet.
Moved by that I decided to write a test that initializes the game engine so that the ship doesn't use fuel to move and just explore the galaxies printing points and what they are in an infinite sequence, then plotting that with the use of a python script i wrote on the go.

import matplotlib.pyplot as plt
import numpy as np

plt.rcParams['xtick.labelsize'] = 8
plt.rcParams['ytick.labelsize'] = 8

asteroidi = [] #here I copied the Asteroids coordinates
fuel = [] #here I copied the FuelPods coordinates

x, y = zip(*asteroidi)
x1,y1 = zip(*fuel)

# Crea una figura più grande
fig, ax = plt.subplots(figsize=(100,30))

# Imposta i limiti degli assi
ax.set_xlim([-300, 17])
ax.set_ylim([0, 17])
ax.set_aspect('equal')

# Disegna gli assi

for i in range(0, 17, 1):
    ax.axhline(y=i, color='gray', linestyle='-')
for i in range(-300, 17, 1):
    ax.axvline(x=i, color='gray', linestyle='-')

for i in range(-320, 17, 16):
    ax.axvline(x=i, color='black', linestyle='--')


ax.set_xticks(range(-300, 17, 1))
ax.set_yticks(range(0, 17, 1))

ax.plot(8,8, 'g*')
ax.plot(x,y, 'ro')
ax.plot(x1,y1, 'bs')

# Aggiungi etichette agli assi
ax.set_xlabel('Asse x')
ax.set_ylabel('Asse y')

plt.savefig('mappa.png')
# Mostra il grafico
plt.show()
Enter fullscreen mode Exit fullscreen mode

Just a simple program to plot a galaxy "strip", in this case the left side, and save it in file

galaxies image

Here just a part of the final map.
With my final map on hand I created a better premade path in terms of "less operations needed to achieve 100 points" and got 92nd place at the end, happy with the final result.

Conclusion

This is just part of the amazing journey that started years ago with Stellar Quest, I've learnt a lot and I'm just excited to see more!

Top comments (0)