Designing the WARoS API

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:

FunctionDescription

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.

Converting the Crobots Intrinsic Functions into a Rust 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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.163.142