# Starter Algo Rust ### File overview starter-algo-rust | ├--algo/src | | | ├--lib.rs | ├--bounds.rs | ├--gameloop.rs | ├--grid.rs | ├--io.rs | ├--map.rs | ├--messages.rs | └--units.rs | └--starter-algo/src | └main.rs ### Creating an algo For starters, simply modify the `starter-algo/src/main.rs` file. You will mostly be using methods of `GameState` and `FrameData`. The easiest way to test your algo locally is to run the `build_local.py` script with a Python interpreter, then run the engine with the path `rust-algo/algo-target`. The `build_local.py` script will build your algo based on the content in `algo.json`, then move the resultant binary to the algo target directory. Our servers will essentially do the same thing when you upload your algo. To upload your algo, upload the entire rust-algo folder on the myalgos page of the terminal site. Note that the max upload size is limited, be carful not to include large build artifacts. The simplest way to create an algo is to create a data type which stores your algo state, implement `GameLoop` on it, and create a main function which calls `run_game_loop` on that type. The are three callbacks for `GameLoop` which you must implement: ```rust fn initialize(&mut self, config: Arc); fn on_action_frame(&mut self, config: Arc, map: &GameState); fn make_move(&mut self, config: Arc, map: &GameState); ``` The `initialize` callback is called once, at the beginning of the game, with the `Config` data that this algo has received and deserialized from the game engine. The `on_action_frame` callback is called every action frame, and is given a `MapState`, an immutable and random-access representation of the state of the game that frame. The `MapState` also contains the deserialized frame data for this frame, including player stats. The `make_move` callback is called every turn frame, and is given mutable access to a `MapState`. Here, the algo should mutate the `MapState` by making valid moves, such as spawning and removing units. The `MapState` records each spawn command that is used to mutate it, and when `make_move` returns, those spawn commands will be submitted to the engine. To generate programming documentation, run cargo doc in rust-algo. Then open index.html in rust-algo/target/doc/starter_algo **The standard output is used to communicate with the game engine, and must not be printed to.** For this reason, debugging must be done through `eprintln!`, and never `println!`. The standard error messages are available on the playground. Some people may find the `stderrlog` logging backend crate useful for this purpose. ### Example algo ```rust extern crate algo; use algo::{ prelude::*, pathfinding::StartedAtWall, }; fn main() { run_game_loop(ExampleAlgo); } /// An example algo which showcases the key features provided in this repository struct ExampleAlgo; impl GameLoop for ExampleAlgo { fn on_turn(&mut self, _: Arc, map: &MapState) { // callback to make a move in the game // try to place as many of four walls as possible for &wall_coord in &[ xy(12, 5), xy(13, 5), xy(14, 5), xy(15, 5), ] { map[wall_coord].try_spawn(StructureUnitType::Wall); } // try to atomically place four scouts in two locations let scout_coord_1 = xy(6, 7); let scout_coord_2 = xy(21, 7); if map[scout_coord_1].can_spawn(MobileUnitType::Scout, 2).yes() && map[scout_coord_2].can_spawn(MobileUnitType::Scout, 2).yes() { for _ in 0..2 { map[scout_coord_1].spawn(MobileUnitType::Scout) .expect("Unexpected spawn failure"); map[scout_coord_2].spawn(MobileUnitType::Scout) .expect("Unexpected spawn failure"); } } // if our cores are low, try to delete a structure if map.frame_data().p1_stats.cores < 5.0 && map[xy(5, 5)].can_remove_structure().yes() { map[xy(5, 5)].remove_structure().unwrap(); } // print the path that an enemy unit would take if spawned at a particular location let move_from = xy(13, 27); match map.pathfind(move_from, MapEdge::BottomRight) { Ok(path) => { eprintln!("Path from [13, 27] = {:?}", path) }, Err(StartedAtWall(_, unit)) => { eprintln!("Enemy slot [13, 27] is blocked by {:?}", unit); } } } } ``` ### Running locally basics 1. Install rust. You can use this [link](https://www.rust-lang.org/tools/install) or search 'Install rust' on a search engine like google and follow the most up to date instructions to install rust on your machine. 2. Compile your algo. Move to the rust-algo working directory, run cargo build. Note that you need to repeat this step to recompile after making changes. 3. Run ```python ../scripts/run_match.py rust-algo/algo-target``` See the README in the scripts folder for more detailed information on running locally. ### Project structure Rust starter algo is organized as a [Cargo workspace](https://doc.rust-lang.org/book/second-edition/ch14-03-cargo-workspaces.html). This workspace starts with 4 packages: - `algo`: a library that contains everything you need to create an algo, in terms of interacting with the game engine. - `starter-algo`: the rust implementation of the starter algo - `example`: the algo code in the readme - `pathtest`: an executable that we use to verify that algos' pathfinding matches the engine pathfinding when we develop new algos To create your own algo, you can either create your own package, or modify the starter-algo code. If you create your own package, you will have to point to it in your `algo.json` metadata file. Rust's `algo.json` file can have these fields: ```rust { "language": "rust", "rust-specific": { "release": false, "package": "starter-algo", "toolchain": "stable", "compile-target": "algo-target" } } ``` - release: whether to run the build in release mode (enable optimizations) - this will make your code run faster, but it will take longer to compile - package: this is the executable package that will be built - change this if you implement your strategy in a new package - toolchain: which rust toolchain to compile with - can be stable, beta, or nightly - compile-target: the directory which will be compiled to, then bundled When you upload your algo to our servers, they will compile your code, and move the binary into the `algo-target` directory with the name `algo`. Any file in the `algo-target` directory at that point, will be accessible when your algo runs. `algo-target` comes with a `run.sh` script, the entry point to all algos. You probably don't need to modify that. If you ever change your build process such that you wish to build to some other directory than `algo-target`, you can simply change the target path in the `compile-target` field of `algo.json`. ### Code Patterns Idiomatic Rust code is often able to use the type system and borrow checker to minimize points of runtime failure, and then use the type system to explicitly denote where these points of failure are. This presents a challenge with creating a terminal algo, since essentially any operation performed in a move can fail for many reasons, including: - An operation was performed on a tile which does not exist - An operation was performed with a unit type which does not make sense - For example, asking how much is costs to spawn the "remove" unit type - Attempting to spawn a unit on a tile which is blocked - Attempting to spawn a unit without sufficient resources The `algo` packages attacks the first two of these problems using the type and borrowing system. #### Unit type The `units` module contains several different types, each of which has variants for a subset of unit types. - `UnitType`: an enum over all unit types, including remove - `StructureUnitType`: an enum over all structure unit types - `MobileUnitType`: an enum over all mobile unit types - `RemoveUnitType`: a `()`-like struct denoting the remove unit type - `SpawnableUnitType`: a union of `StructureUnitType` and `MobileUnitType` These types are convertible into each other, both fallibly and infallibly, through the `Into` trait. For example, All unit types implement `Into`. Both `StructureUnitType`, `MobileUnitType`, and `SpawnableUnitType` implement `Into`, but `RemoveUnitType` does not. This type system allows code which deals with units to be restrictive at compile-time over which unit types are allowed. For example, the `Map`'s `cost_of` function accepts an `impl Into`, allowing it to be called with any `MobileUnitType` or `StructureUnitType`, but never a `RemoveUnitType`.