The original Crobots API was defined by what Poindexter called the Intrinsic Function Library.[43] Each of these functions were made available to the original robots to allow them to interact with (and hopefully dominate) their virtual environment. If you’re thinking that this sounds a lot like a list of imports required by a WebAssembly module, you’re right. As soon as I remembered the Crobots intrinsic functions, I knew I had to try and port it to WebAssembly.
Let’s take a look at the original list of intrinsic functions:
Function | Description |
---|---|
scan(deg, res) | Invokes the robot’s scanner, pointing it at a specified degree and resolution. Returns 0 if there are no robots in range. Otherwise returns a positive integer indicating the distance to the closest target in that direction. |
cannon(deg, range) | Fires the cannon at a specified degree and range. Range is limited to 700m. Returns 0 if no missile was fired, 1 if a missile was fired. |
drive(deg, speed) | Engages the drive motor in the specified direction at the indicated percentage of power. |
damage() | Returns the percentage of damage currently taken by the robot. |
speed() | Returns the current speed of the robot as a percent. Check this value as it can differ from the parameter given to drive because of acceleration, deceleration, and collisions |
loc_x() loc_y() | Returns the current x- or y-coordinate. |
rand(limit) | Returns a random number between 0 and limit, with a max of 32,767 |
sqrt(number) | Returns the square root of a number, coerced positive if necessary |
sin(deg) cos(deg) tan(deg) atan(deg) | Trig functions. In the original Crobots game, in-memory trig tables and a large scale were used to avoid floating-point calculations and roundoff problems. We shouldn’t need to use any of those tricks with our API. |
When I first went back and looked at the Crobots documentation, I was struck by the elegance of the intrinsic function library. If you take a close look, you’ll see that all of the functions already adhere to the basic rules that we know apply to WebAssembly modules—nothing takes or returns any parameter that isn’t a 32-bit integer, and there are no complex types or tuples used.
WebAssembly has floating-point numbers, but this list of functions has been working quite well for decades, so let’s stick with it and see how well it works out for a modern game. The first step is converting these functions into a list of extern functions.
To start off, let’s create a workspace in a root directory (I called mine waros) and then create a new Rust library project beneath that called warsdk. This will be a library used to wrap extern functions and provide a few other utilities to WebAssembly robot modules.
Ignore the lib.rs for now and create a new file called ffi.rs. FFI stands for Foreign Function Interface, and the extern declarations are part of Rust’s FFI:
| extern "C" { |
| pub fn scan(angle: i32, resolution: i32) -> i32; |
| pub fn cannon(angle: i32, range: i32) -> i32; |
| pub fn drive(angle: i32, speed: i32) -> i32; |
| pub fn damage() -> i32; |
| pub fn speed() -> i32; |
| pub fn loc_x() -> i32; |
| pub fn loc_y() -> i32; |
| pub fn rand(limit: i32) -> i32; |
| pub fn wsqrt(number: i32) -> i32; |
| pub fn wsin(degree: i32) -> i32; |
| pub fn wcos(degree: i32) -> i32; |
| pub fn wtan(degree: i32) -> i32; |
| pub fn watan(degree: i32) -> i32; |
| pub fn plot_course(tx: i32, ty: i32) -> i32; |
| } |
Most of this looks identical to Poindexter’s original intrinsic function list, with a few minor changes. First, I had to add a prefix to a few of the math functions because, after a recent Rust update, functions like sin and cos were made part of a globally visible set of names and wouldn’t link to WebAssembly anymore.
Second, I added a function called plot_course. This just determines the heading from the robot’s current location to a target location. Nearly every Robot that chooses a destination coordinate will need this function, so I made it part of the API for convenience.
This looks like a pretty simple API, and it should be easy for developers working in Rust, Go, raw WebAssembly, or any other language to build robots compatible with the host. People building robots in lower-level languages will be thankful for the availability of the trig functions and it also helps us ensure that robots from different languages behave consistently within the same host.
I placed these extern functions in their own FFI module so that the name exposed to the SDK consumers (the robot developers) could have the same names and still wrap the external calls in unsafe. Modify the lib.rs file of the warsdk project to look like this:
| pub fn scan(angle: i32, resolution: i32) -> i32 { |
| unsafe { ffi::scan(angle, resolution) } |
| } |
| |
| pub fn cannon(angle: i32, range: i32) -> i32 { |
| unsafe { ffi::cannon(angle, range) } |
| } |
| |
| pub fn drive(angle: i32, speed: i32) -> i32 { |
| unsafe { ffi::drive(angle, speed) } |
| } |
| |
| pub fn damage() -> i32 { |
| unsafe { ffi::damage() } |
| } |
| |
| pub fn speed() -> i32 { |
| unsafe { ffi::speed() } |
| } |
| |
| pub fn loc_x() -> i32 { |
| unsafe { ffi::loc_x() } |
| } |
| |
| pub fn loc_y() -> i32 { |
| unsafe { ffi::loc_y() } |
| } |
| |
| pub fn rand(limit: i32) -> i32 { |
| unsafe { ffi::rand(limit) } |
| } |
| |
| pub fn wsqrt(number: i32) -> i32 { |
| unsafe { ffi::wsqrt(number) } |
| } |
| |
| pub fn wsin(degree: i32) -> i32 { |
| unsafe { ffi::wsin(degree) } |
| } |
| pub fn wcos(degree: i32) -> i32 { |
| unsafe { ffi::wcos(degree) } |
| } |
| |
| pub fn wtan(degree: i32) -> i32 { |
| unsafe { ffi::wtan(degree) } |
| } |
| |
| pub fn watan(degree: i32) -> i32 { |
| unsafe { ffi::watan(degree) } |
| } |
| |
| pub fn plot_course(tx: i32, ty: i32) -> i32 { |
| unsafe { ffi::plot_course(tx, ty) } |
| } |
| |
| // Utility sample for moving to destination and stopping |
| // Note - does NOT recover from collision en route |
| pub fn go(target_x: i32, target_y: i32) { |
| let course = plot_course(target_x, target_y); |
| drive(course, 20); |
| // at speed 20, it should take 2 ticks from awareness |
| // of the target to stop on it |
| while (target_x - loc_x()).abs() > 40 && |
| (target_y - loc_y()).abs() > 40 && |
| speed() > 0 { |
| // wait till we get to the target |
| } |
| |
| drive(course, 0); // turn off engine |
| while speed() > 0 { |
| // steady on until we stop |
| } |
| } |
| |
| pub const ANGLE_EAST: i32 = 0; |
| pub const ANGLE_NORTH: i32 = 90; |
| pub const ANGLE_WEST: i32 = 180; |
| pub const ANGLE_SOUTH: i32 = 270; |
| |
| pub const MAX_X: u32 = 1000; |
| pub const MAX_Y: u32 = 1000; |
| |
| pub const DAMAGE_COLLISION: u32 = 2; |
| pub const DAMAGE_DIRECTHIT: u32 = 10; |
| pub const DAMAGE_NEARHIT: u32 = 5; |
| pub const DAMAGE_FAR_HIT: u32 = 3; |
| |
| pub const BLAST_RADIUS: i32 = 40; |
| |
| pub const PROJECTILE_MAX_RANGE: u32 = 200; |
| |
| mod ffi; |
Most of this is just simple unsafe wrappers around the extern functions. However, I’ve added a handy little utility function called go that was useful enough to include in the SDK rather than individual robots. There are also a couple of constants available to help robot developers write logic. Be warned, though—these aren’t the constants built into the game engine itself, so there’s a risk of getting them out of sync. This might be an issue if you were publishing this SDK outside of book samples. Each time you want to build a robot in WebAssembly and Rust, you’ll just declare a dependency on this SDK.
Finally, the Cargo.toml can stay the way it came out of the initial creation. Add a line to your workspace’s root Cargo.toml so it looks like this:
| [workspace] |
| members = [ |
| "warsdk", |
| ] |
Now everything we’re going to build in this chapter (and there will be a lot) will build to the root-level target directory and you can run test suites at the top level for all of your projects.
18.118.163.142