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.
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.
100 points
by shooting and destroying asteroids16x16
galaxy grids
6 asteroids
are contained within each galaxy2 fuel pods
are contained within each galaxyp_turn
, p_move
, p_shoot
, p_harvest
, and p_upgrade
functions
to control your shipFast, 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.
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.
This round of fca00c will run according to the following schedule:
Date | Time (EST) | Unix Timestamp | What's Happening? |
---|---|---|---|
2023-02-15 | 7:00pm | 1676505600 | fca00c-asteroids goes live! All leaderboards are open for entries. |
2023-02-22 | 7:00pm | 1677110400 | Submission deadline for Smallest WASM leaderboard. |
2023-03-01 | 7:00pm | 1677715200 | Submission deadline for Fastest Submission and Lowest Resource Use leaderboards. |
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.
Before you can begin writing your contract, you'll need some materials first.
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.
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:
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.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 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
and2 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.
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.
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.
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:
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°).Direction
argument to the p_turn()
method, specifying
the new direction you'd like your ship to face:
engine.p_turn(&Direction::Left)
.p_turn()
method consumes a flat cost of 1 fuel
,
even if you turn 315°.p_move()
method to move your ship. By default, your
ship will move 1 space
in the direction it is facing.engine.p_move(Some(4))
.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.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.
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.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
.p_upgrade()
method to upgrade your ship at any point
during your quest. Upgrading will cost 5 points
.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.
soroban-cli
to optimize a compiled contract using some sensible defaults.wasm_opt
crate: This can be used to further customize
and optimize your compiled contract.Binaryen
toolkit: Binaryen is a compiler and toolchain for
WebAssembly. This toolkit is used "under the hood" in the wasm_opt
crate.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.
GameEngine
Initialization ParametersWhen 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 defaultlaser_range (3)
: The maximum distance from which your ship's laser can
p_shoot()
an asteroidseed (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 gridfuel: ()
: 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 initializationshoot_fuel (5)
: The amount of fuel consumed by the p_shoot()
methodmove_fuel (2)
: The amount of fuel consumed when you p_move()
a single
spaceturn_fuel (1)
: The amount of fuel consumed by the p_turn()
methodasteroid_reward (1)
: The number of points you are rewarded for destroying an
asteroidasteroid_density (6)
: The number of asteroids each galaxy will containpod_density (2)
: The number of fuel pods each galaxy will containIf 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!
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.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.
fca00c_fast()
test will test against your written contract source code,
and is a much quicker way to iterate throughout the build process.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.
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.
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.