Creating WebAssembly Robots

Now for the fun part: creating WebAssembly robots! We’ve built an engine that can load WebAssembly modules into memory, expose a runtime host for them, and safely allow them to interact with a virtual environment and manipulate shared state. Now it’s time to exercise that engine by building some robots.

First, let’s create a fairly dumb target. I wanted a “read hungry” robot to ensure that no matter how frequently a robot queried its own state, it couldn’t cause a threading dead lock in the system. Create a new library Rust module called dumbotrs and add it to the workspace. Its Cargo.toml should look like the following:

 [package]
 name = ​"dumbotrs"
 version = ​"0.1.0"
 authors = [​"Your Name <[email protected]>"​]
 
 [lib]
 crate-type = ​["cdylib"]
 
 [dependencies]
 warsdk = ​{​ ​path​ ​=​ ​"../warsdk"​ ​}

This robot only depends on the SDK we created earlier that exposes the host functions. Let’s take a look at lib.rs:

 extern​ crate warsdk;
 use​ ​warsdk​::*;
 
 #[no_mangle]
 pub​ ​extern​ ​"C"​ ​fn​ ​botinit​() ​->​ i32 {
 drive​(90, 10);
 
 loop​ {
 damage​();
 speed​();
  }
 }

This robot, regardless of its starting position, drives north at a speed of 10%. It then goes into an infinite loop where it continually queries its own state. This robot should eventually collide with the northern wall and stop, where it will keep eagerly consuming its state.

Since we will probably never need to compile this robot for anything other than the wasm32-unknown-unknown target, we can tell Cargo to default to that by creating a .cargo/config file inside the project directory:

 [build]
 target = "wasm32-unknown-unknown"

Creating the Rook

The Rook, inspired by Poindexter’s original implementation,[49] is a robot that seeks the middle of the game board. Once there, it will move laterally east and west, scanning for potential targets at the four compass points. Once it finds one, it stops and fires at it until there are no more targets in range.

This is a subtle yet fairly powerful strategy. If an opposing robot is anywhere but the extreme top or bottom of the game board, it will eventually be detected by the rook’s scanners and fired upon. The only escape is to keep moving or to kill the rook.

Create a new library Rust project called rook, add it to the workspace, and set its default compilation target to wasm32-unknown-unknown. Update its Cargo.toml to include a relative path dependency on the warsdk code. Let’s take a look at what the rook looks like written in Rust:

 /* Inspired by https://github.com/tpoindex/crobots/blob/master/src/rook.r
  *
  * Will move to the center of the field and then patrol from East to West,
  * scanning all four compass points for targets. If it is hit while scanning
  * it will change direction
  *
  * Note: this rook ignores incoming fire while on its way to the center of the
  * battlefield.
  */
 extern​ crate warsdk;
 use​ ​warsdk​::*;
 
 struct​ State {
  course: i32,
 }
 
 #[no_mangle]
 pub​ ​extern​ ​"C"​ ​fn​ ​botinit​() ​->​ i32 {
 go​(500, 500);
 
 let​ ​mut​ state = State { course: 0 };
 
 loop​ {
 look​(ANGLE_EAST, &​mut​ state);
 look​(ANGLE_NORTH, &​mut​ state);
 look​(ANGLE_WEST, &​mut​ state);
 look​(ANGLE_SOUTH, &​mut​ state);
 
 if​ ​loc_x​() > BOUND_X_MAX {
 reverse​(&​mut​ state);
  }
 
 if​ ​loc_x​() < BOUND_X_MIN {
 reverse​(&​mut​ state);
  }
 
 if​ ​speed​() == 0 {
 // bumped into something
 reverse​(&​mut​ state);
  }
  }
 }
 
 fn​ ​look​(angle: i32, state: &​mut​ State) {
 let​ ​mut​ range = ​scan​(angle, 2);
 
 // Fire at targets in range until we have no targets in range
 while​ range > 0 && range < PROJECTILE_MAX_RANGE ​as​ i32 {
 if​ ​speed​() > 0 {
 drive​(state.course, 0);
  }
 
 if​ range > BLAST_RADIUS {
 // don't want to blow ourselves up!
 cannon​(angle, range);
  }
  range = ​scan​(angle, 2);
  }
 }
 
 fn​ ​reverse​(state: &​mut​ State) {
 if​ state.course == ANGLE_EAST {
  state.course = ANGLE_WEST;
  } ​else​ {
  state.course = ANGLE_EAST;
  }
 }
 
 const​ BOUND_X_MIN: i32 = 80;
 const​ BOUND_X_MAX: i32 = 920;

In this robot’s implementation, you can see we now have our own internal, mutable state. This is a really powerful capability, because it means that the robots can make complex decisions based on observations from previous iterations through their loops. In the case of the rook, it maintains the direction it’s facing so it can reverse it when it reaches a boundary near the eastern or western edges of the game board.

Cry Havoc, and Let Slip the Robots of War!

You’ve got a game engine, and you’ve got a couple of robots (as well as a couple included in the code not listed in the chapter). It’s time to pit them against each other and see what happens. To do this, we’ll create another project in the workspace called consolerunner, a CLI application that loads wasm modules and invokes the engine:

 extern​ crate botengine;
 use​ ​botengine​::{Combatant, Gameloop};
 use​ ​std​::​sync​::​mpsc​::channel;
 use​ ​std​::​sync​::Arc;
 use​ ​std​::thread;
 use​ ​std​::time;
 
 fn​ ​main​() {
 let​ gs = ​Arc​::​new​(​botengine​::​GameState​::​new​());
 
 let​ b1 = ​botengine​::​Combatant​::​buffer_from_file​(
 "./bots/dumbotrs.wasm"​);
 let​ bot1 = b1​.unwrap​();
 
 let​ b2 = ​botengine​::​Combatant​::​buffer_from_file​(
 "./bots/rook.wasm"​);
 let​ bot2 = b2​.unwrap​();
 
 let​ rb = ​botengine​::​Combatant​::​buffer_from_file​(
 "./bots/rabbit.wasm"​);
 let​ rabbit = rb​.unwrap​();
 
 let​ my_gs = gs​.clone​();
 let​ debug_gs = gs​.clone​();
 
 let​ (sender, receiver) = ​channel​();
 thread​::​spawn​(​move​ || ​loop​ {
 match​ receiver​.recv​() {
 Ok​(ge) ​=>​ println!(​"{:?}"​, ge),
 Err​(_) ​=>​ {}
  }
  });
 
 let​ ​mut​ gl = ​Gameloop​::​new​(my_gs, 100_000, 3, ​Some​(sender));
 
 let​ _handle = ​Combatant​::​start​(​"bot-1"​, bot1, gs​.clone​());
 let​ _handle2 = ​Combatant​::​start​(​"rook"​, bot2, gs​.clone​());
 let​ _handle3 = ​Combatant​::​start​(​"rabbit"​, rabbit, gs​.clone​());
 let​ game_result = gl​.start​();
 
 thread​::​sleep​(​time​::​Duration​::​from_secs​(1));
 
  println!(
 "Game loop terminated: {:?}​​ ​​State: {:?}"​,
  game_result, debug_gs
  );
 }

This code loads three robots from the bots directory (the files were just copied from target output of other modules), creates the GameState, and shares clones of the Arc of that state with instances of the Combatant struct.

When all is ready to go, we invoke start on the game loop and wait for the match to resolve after 100,000 frames or cycles. You’ll also see that I’ve created a channel and set up a thread where I await events in an infinite loop. This prints them to the console, but you can imagine a whole host of other things we could do with the list of game events.

Run the application either by compiling and running the binary or simply with cargo run. You’ll see a ton of spam that comes from the rook as it launches missiles at the rabbit as the rabbit bounces randomly across the game field. Since the rabbit never spends more than a single frame sitting in a spot, it usually manages to escape every match almost completely unharmed, save for a few unfortunate collisions with walls. Poor dumbotrs heads north, hits a wall, incurs two points of damage, and then spends the rest of the match idle.

Congratulations! You now have a fully functioning game engine inspired by the original Crobots that pits multiple WebAssembly modules against each other. This can be used to teach people how to write Rust WebAssembly modules as well as for a lot of fun and senseless virtual battlefield violence, and to illustrate how to allocate dedicated threads to WebAssembly host runtimes.

Room for Improvement

There’s plenty more that you could do with this game engine. Most of it, however, has little to do with WebAssembly itself. You could add a new system and component pair that allocates points to players every time they do damage to another player. You could then create yet another system and component pair that can declare victory and terminate a match early when only a single robot is left standing.

I hope you will see places where you can have fun improving this code to continue learning Rust and WebAssembly.

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

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