Game Logo

How to play

Asteroids

Collect 100 points by shooting asteroids

Today's game will set you on a spaceship in one of the darkest corners of the universe! It is your quest to explore this uncharted portion of the universe and destroy 100 asteroids. Don't forget to re-fuel and upgrade your ship along the way.

TL;DR

Provided here are the bullet points of what you will absolutely need to know to successfully play this game. This is intentionally quite brief, and everything is explained in much greater detail further on down in this document.

  • Your goal is to earn 100 points by shooting and destroying asteroids
  • You will need to navigate your ship through multiple 16x16 galaxy grids
    • 6 asteroids are contained within each galaxy
    • 2 fuel pods are contained within each galaxy
  • Use the p_turn, p_move, p_shoot, p_harvest, and p_upgrade functions to control your ship
  • Turning, moving, and shooting all consume different amounts of fuel
  • Upgrade your ship to improve fuel efficiency
  • Upgrading consumes points

Competition Leaderboards and Schedule

Fast, Cheap, and 0ut 0f Control is a wild, experimental take on coding competitions. Your goal is to submit a working contract that solves a given problem, or performs a given task. We will track results in the following three (3) leaderboards.

  • Fastest Submission: On this leaderboard, the top prizes go to the entrants submitting a complete contract first, second, and so on. A good, old-fashioned race!
  • Smallest WASM: We want to test the limits of how small these deployed contracts can be. We're awarding prizes for valid contracts with the very smallest compiled sizes.
  • Lowest Resource Use: We are measuring resource use as the number of CPU instructions used during a contract's invocation. Top spots are given to complete contracts with the least CPU utilization.

Each time you submit a contract, it will be judged according to all leaderboards currently accepting entries. Your position in each leaderboard is determined by your best submission to that particular leaderboard.

  • Yes! You can submit more than once. You probably should, if you want to be competitive within each leaderboard.
  • Yes! You can earn a prize from each leaderboard. Specific award amounts can be found in the official fca00c rules.

This round of fca00c will run according to the following schedule:

DateTime (EST)Unix TimestampWhat's Happening?
2023-02-157:00pm1676505600fca00c-asteroids goes live! All leaderboards are open for entries.
2023-02-227:00pm1677110400Submission deadline for Smallest WASM leaderboard.
2023-03-017:00pm1677715200Submission deadline for Fastest Submission and Lowest Resource Use leaderboards.

Get Started with Soroban

As a very brief precursor before we get too deep into the weeds, here is a quick cheatsheet of references, links, and resources you might find useful if you are confused by the words Soroban, Stellar, Rust, or anything else we discuss.

  • Soroban Documentation: This is a great first step to learn more about Stellar's smart contract platform.
  • Soroban SDK Crate: The official Rust SDK for interacting with the Soroban platform.
  • The Rust Book: The definitive volume for learning the Rust programming language.
  • Rustlings: A more interactive method for learning Rust.
  • Soroban Quest: Soroban Quest is an interactive course all about Soroban.
    • ⚠️ Due to Soroban's current alpha nature, this course may or may not be fully up-to-date, but it will provide some valuable context.
  • Stellar Quest: Soroban lives on top of the Stellar network. Writing smart contracts for Soroban doesn't require an in-depth knowledge of Stellar, but the context can be useful.

How to Play

Gather Your Materials

Before you can begin writing your contract, you'll need some materials first.

  1. Setup your Soroban development environment using this guide from the Soroban documentation.
  2. Git clone the stellar/fca00c-asteroids repo. This is the canonical source for information and materials used for this challenge.

If you are reading this document anywhere besides the fca00c site (in a code editor, for example), you've probably already done everything above, so you get to skip right to the front of the line. Well done!

Note: The competition materials may be updated, changed, etc. from time to time. If you've cloned the git repository, we recommend you git pull often. If something seems to be broken, not working as expected, etc. checking for an up-to-date repo is an excellent first step.

The Game Engine Contract

We have built a Game Engine contract. Your task is to write a contract that will interact with our game engine in the Soroban environment. This contract has been included in two formats:

  1. In the contracts/_game-engine directory, we've provided the source code for the contract, broken into its various modules. This version of the contract is NOT intended to be modified, or used in your contract. It's being provided only as a resource and reference to aid in understanding and problem-solving.
  2. As a compiled WASM binary: contracts/game_engine.wasm. This is the version of the contract you'll want to build and test your solution with. The starter tests we've provided in the solution directory will mimic our evaluation environment as closely as possible.

If you want to investigate the compiled binary, the soroban contract bindings command will give you a pretty decent understanding. soroban contract bindings is intended to generate client bindings for a contract. This will give you great insight into what functions exist in a given WASM file, and what arguments they are expecting. You can run the command like this:

soroban contract bindings --wasm /path/to/game_engine.wasm --output rust

The Map

The map in our asteroids game is an infinite cartesian plane (yes, this universe is "flat," don't worry about it. You should be focusing on the game, anyway). This plane is then divided into "galaxies." Each galaxy is a 16x16 square. Note the difference between 16 squares and 17 points along either the x or y axis that makes up each galaxy. For example, your ship starts its journey in the galaxy that has a center coordinate of (8, 8), and it is comprised of all points within the following coordinates:

BottomLeft: (0, 0)
BottomRight: (16, 0)
TopRight: (16, 16)
TopLeft: (0, 16)

Each galaxy will contain 6 asteroids and 2 fuel pods. You can only see or shoot asteroids that are inside your current galaxy. When the time comes to change galaxies, you must move your ship outside the boundaries of your current galaxy.

Note: It would be more technically correct to say, "Each galaxy will usually contain 6 asteroids and 2 fuel pods." It is unlikely, but possible that our game engine contract will generate a set of colliding coordinates for two elements (asteroids or fuel pods). In this improbable circumstance, a fuel pod will take priority over an asteroid, and there will be only one element left at this point ("erasing" the previous element). A galaxy like this would contain fewer than 8 map elements.

Expand Map Visualizations

Below, a single galaxy is illustrated. After the game engine has been first initialized, your ship is placed at (8, 8) within the first galaxy.

A single galaxy in the asteroids game

Even though you can only see one galaxy at a time, there are neighboring galaxies that contain their own asteroids and fuel pods. These are only accessible to you once your ship has entered that next galaxy.

Multiple galaxies in the asteroids game

Controlling Your Ship

Your vessel is outfitted with the most bleeding-edge capabilities we could cram into a starship! Your ship is capable of performing all these actions:

  • Turn: Your ship can turn in any direction you choose. Turning does cost fuel, though. It will cost you the same amount of fuel, no matter which direction you are turning to.
    • Use the game engine's p_turn() method to turn your ship in the desired direction before you shoot or move. Each turn will cost 1 fuel, no matter how far you are turning (i.e., turning 45° costs the same as turning 180°).
    • You must supply a Direction argument to the p_turn() method, specifying the new direction you'd like your ship to face: engine.p_turn(&Direction::Left).
    • Remember, calling the p_turn() method consumes a flat cost of 1 fuel, even if you turn 315°.
    • Read below for more details on diagonal turns.
  • Move: Your ship can move any number of spaces as long as you have enough fuel to cover that cost. Each space you move costs fuel as well (twice as much fuel as each turn, mind you).
    • Use the game engine's p_move() method to move your ship. By default, your ship will move 1 space in the direction it is facing.
    • You can (optionally) provide a number of spaces you'd like your ship to move: engine.p_move(Some(4)).
    • Remember that moving will cost 2 fuel for every space moved, no matter how many times p_move() is called, or how many spaces you provide to it as an argument. The calculation is always made on the number of spaces your ship moves.
  • Shoot Asteroids: Your ship is capable of shooting asteroids to destroy them and earn valuable points! For each asteroid destroyed, you will be rewarded with 1 point. Your ship's laser cannon has a range of 3 spaces. For example, if your ship is currently located at (53, 72), you could shoot and destroy asteroids located at (53, 75) or (50, 75); however, an asteroid located at (49, 72) would be out of range.
    • Use the game engine's p_shoot() method to shoot your laser cannon in the direction your ship is currently facing. Each time you fire the cannon will cost 5 fuel, whether you hit an asteroid or not.
    • If your ship is facing in a diagonal direction, you can shoot asteroids within range of that direction. This can save you fuel and execution costs by minimizing the number of moves needed to get within range of an asteroid.
    • You can shoot asteroids sharing the same coordinates as your ship (i.e., an asteroid you're "on top of").
    • You can shoot multiple asteroids in one shot in the same direction, provided they are all within range of your ship (i.e., you shoot in a straight line, and any asteroids on that line will be destroyed).
  • Harvest Fuel Pods: The amount of fuel you begin with will not be enough to complete this quest. Sorry! Fuel prices are quite high at the moment. So, along the way, you will need to harvest fuel pods to recharge your ship.
    • Use the game engine's p_harvest() method to harvest a fuel pod once you have moved your ship to the fuel pod's coordinates. Each fuel pod harvested will give your ship an additional 100 fuel.
  • Upgrade: Your ship even comes with its own upgrade feature. You can upgrade your ship only once, and you can do so at any point during your quest (only you can determine when the time is right). You will have to give up some of the points you've worked hard to earn, but after the upgrade is complete your ship will use half the fuel for turning, moving, and shoooting.
    • Use the game engine's p_upgrade() method to upgrade your ship at any point during your quest. Upgrading will cost 5 points.

Compile Your Contract

When you've written a working contract, you'll need to build a binary file that will contain your contract's WASM byte-code. This executable file is what you are meant to submit as "Your Ship" on the fca00c site.

Compilation can be done many different ways, but we've provided a couple commands in our Makefile to get you going. make build will produce a binary compiled according to the "release" profile, while make build-optimized will work to optimize that build and minimize the size of the .wasm file.

You can also choose from several different optimization strategies to produce an efficient contract binary. There is much more nuance here than we have space to get into fully. However, you can use the following links to get started learning more.

  • Optimizing Builds: This example shows how to use the soroban-cli to optimize a compiled contract using some sensible defaults.
  • The wasm_opt crate: This can be used to further customize and optimize your compiled contract.
  • The Binaryen toolkit: Binaryen is a compiler and toolchain for WebAssembly. This toolkit is used "under the hood" in the wasm_opt crate.

Submit Your Ship

You're finished! Really!? Sweet!! You should be proud of yourself, just for getting to this part!

When you're ready to have your smart contract evaluated, your next step is to upload your compiled WASM file on the fca00c site. You'll have the option to log in and select how your name will be displayed if you land on the leaderboard, and you can choose the file you wish to upload. We'll take care of everything else from there!

Note: The Soroban environment we run in our backend will limit your contract invocation to 30 seconds of runtime, and 16_000_000_000 CPU cycles. If your contract runs past these limits, your validation will fail.

Useful Information

GameEngine Initialization Parameters

When we are testing your solution contract, we will run it against our game engine with a prescribed and consistent set of initialization values. These have been documented in the test.rs file, but we'll include our initialization values and some brief descriptions here, as well:

  • move_step (1): The number of spaces your ship will p_move() by default
  • laser_range (3): The maximum distance from which your ship's laser can p_shoot() an asteroid
  • seed (8891): The map's randomness is seeded with a known, consistent u64 value (this ensures everyone is playing on the same map)
  • view_range (16): The size of each galaxy grid
  • fuel: (): Soroban functions can only accept a maximum of 10 parameters, so all the fuel parameters are collected here
    • player_fuel (50): The amount of fuel your ship contains at initialization
    • shoot_fuel (5): The amount of fuel consumed by the p_shoot() method
    • move_fuel (2): The amount of fuel consumed when you p_move() a single space
    • turn_fuel (1): The amount of fuel consumed by the p_turn() method
  • asteroid_reward (1): The number of points you are rewarded for destroying an asteroid
  • asteroid_density (6): The number of asteroids each galaxy will contain
  • pod_density (2): The number of fuel pods each galaxy will contain

Diagonal Turns and Moves

If you take a look at the game engine's type definitions, you may notice the Direction type looks like this:

pub enum Direction {
    Up,
    UpRight,
    Right,
    DownRight,
    Down,
    DownLeft,
    Left,
    UpLeft,
}

From the presence of directions like UpRight and DownLeft, you could infer (correctly) that your ship is capable of turning and moving diagonally. You can boost your ship's efficiency by moving diagonally when that is called for.

For everyone's sake, we've simplified the calculations in the game contract. 1 unit of diagonal movement is the same as 1 unit of horizontal or vertical movement (We know, things can get weird out there in space).

For example, if your ship is currently pointing Up at (0, 0), and you want to move your ship to (3, 2), here's how this can help:

# Fuel Use Without Diagonal Moves
p_move(2) + p_turn(Right) + p_move(3) = 4 + 1 + 6 = 11 fuel

# Fuel Use With Diagonal Moves
p_turn(UpRight) + p_move(2) + p_turn(Right) + p_move(1) = 1 + 4 + 1 + 2 = 8 fuel

You can also shoot asteroids in diagonal directions, as long as your ship is facing that direction and they are within range. This is a very powerful fuel-saving technique!

Helpful Game Engine Methods

The game engine contract provides some helper methods so you can orient yourself in space and monitor the other vital information about your ship:

  • p_pos(): Returns the ship's position on the map, as a set of coordinates.
  • p_dir(): Returns the direction your ship is currently pointed in.
  • p_fuel(): Returns the ship's current fuel level.
  • p_points(): Returns the player's current score.
  • get_map(): Returns the current galaxy's map as Map<Point, MapElement>, where MapElement will be either an asteroid or fuel pod.

Testing Your Contract

While writing your contract, you'll likely want to incorporate some tests along the way. The src/test.rs file is there for that. We've included two test functions to get you started.

  • The fca00c_fast() test will test against your written contract source code, and is a much quicker way to iterate throughout the build process.
  • The fca00c_budget() test will test against a compiled WASM contract binary. Of course, for this to work, you will need a compiled contract in place first. You can run make build or make build-optimized (or, you can do it yourself with cargo build, provided you know how to use it).

You'll want to keep our original fca00c_fast() and fca00c_budget() functions intact, but you can most definitely write your own. In fact, many people may find it easier to write their initial solution inside a test.rs file before building the final compiled WASM binary.

Inside the testing environment, you can access very useful things from the std crate, such as the println! macro. If you want to output any data along your development road, it's likely that including it in a test will be the quickest and easiest way to go about it.

Note: Rust tests do not print by default. So, if you're using println, you will need to run the test like: cargo test -- --nocapture. This will send any test output to stdout.

Then, after you've written a working solution, it's pretty easy to get it moved into your lib.rs file. If you want to get some kind of output from your lib.rs file, you'll need to learn all about logging and debugging in Soroban. There is a lot there, but it's useful knowledge that can help you get the information you need, right from where you need it.

Suggestions and Strategies

There is absolutely no shortage of interesting and unique methods you could use to solve this problem. Fast, Cheap, and 0ut 0f Control is designed to allow for many various competitive strategies. When trying to compete for pure speed and get the contract written before others, you'll likely care far less about optimizing your contract's performance or execution cost. Similarly, when you're aiming for the very cheapest execution cost, the final deployed contract size may not be a primary concern of yours. Your chosen strategy will need to reflect the relevant competition leaderboard you're currently optimizing for.

To get you started, we are providing below some suggestions and possible strategies you might consider using. Take them, leave them, adapt them based on your competitive context: The choice is yours.

  • This game's universe is infinite! It might be possible to move in one single direction, blasting any asteroids that happen to be right in front of you, and come out victorious. That's very unlikely to work out in a cost- or time-efficient manner, but you're welcome to give it a shot.
  • The get_map() function does quite a bit of calculating, and even accesses a few different contract storage entries. Perhaps (or, perhaps not...) it would be more efficient to keep track of what is in your galaxy's map on your own, rather than calling get_map() repeatedly for the same galaxy.
  • Perhaps more important than anything else in this game is to think through carefully how you will approach navigating. How far in advance do you want to know the layout of your galaxy (or multiple galaxies)? Which asteroid is the best next target? How low is too low for your fuel before you start looking for a refill? All these questions can only be answered by you, and your answers will determine your level of success in this game.
  • Before navigating to harvest a fuel pod, you might want to shoot any nearby asteroids first, or shoot any you pass along the way (provided you have enough fuel, that is).
  • Once you've harvested all fuel pods and destroyed all the asteroids in a galaxy, it is time to move on to the next one! However, which galaxy to move to is certainly not a trivial choice. Plan carefully how you want to navigate, considering which neighboring galaxy provides the closest, cheapest, and/or fastest path of entry.
  • When you set out for a fuel pod, make sure you're heading toward the nearest one to your current location.

Bottom Detail LineBottom Detail LogoBottom Detail Line