Programming Your Team

The code you write for your team is executed once for each ship, when the ship is born. In your code, you must define an ai.step function. That function is then called on each of your living ships, throughout the game.

Here is a simple, entire, Nodewar program:

ai.step = function(o) { return { torque: 1.0, // max torque thrust: 1.0 // max thrust }; };

Alternatively, you can write in CoffeeScript:

ai.step = (o) -> { torque: 1.0 thrust: 1.0 }

Other Languages! We'll be adding ClojureScript and other compile-to-JS languages soon.

As you can see, your ai.step function can return torque and thrust values, which are how you control you ship. Not returning a torque or thrust will leave your ship's torque or thrust unchanged. So, technically, this is the simplest Nodewar AI:

ai.step = function(o) { return {}; };

There are 4 items you can return from your ai.step function, inside a single object:

  • thrust : [0..1] - the "front" of a ship is its sharpest vertex, so this accelerates a ship towards its deadliest weapon.
  • torque : [-1..1] - torque to apply to the ship. This is how you turn your ship.
  • label : a string to label your ship when drawing. Setting null removes it.
  • log : a string to print to console.log. Or, just as easy, you can call console.log wherever you like in your code.

Note that thrust and torque are normalized, where 0 is off, and 1 (or -1 for torque) is maximum. The values you return are actually multiplied by o.const.MAX_THRUST and o.const.MAX_TORQUE, which you can access, if you want to think in terms of newtons or newton-meters.

Here is a slightly more complicated AI, demonstrating how you might use all these functions.

ai.step = function(o) { if (o.me.queen) { return stepQueen(o); } else { return stepOther(o); } }; stepQueen = function(o) { return { thrust: 1, torque: 1, label: "Queen Bee", log: "Queen Bee checking in: the game time is " + o.game.time }; }; stepOther = function(o) { return { torque: -1 }; };

Maintaining State

When a ship is created, its AI genetic code is executed once. In addition to defining your ai.step function, you can create other functions and variables. For example, a variable scoped outside your ai.step function allows it to be shared by each step call.

This code increments a counter and attaches it as a label to your ship.

var counter = 0; // a "global" variable ai.step = function(o) { return { thrust: 1000, label: "Processed " + (++counter) + " steps" }; };

State may be shared among all your ships, together, using o.mothership. The object o is explained in the rest of this document.

The API

The o object passed to your ai.step function has a lot of good stuff in it, including stats about the ship being stepped, the other ships, the moons, the game state, and even some helper functions. You should get to know it.

For your convenience although your battle happens in a triple moon system, Nodewar units are scaled from astronomical values to something more human understandable:

  • distances are in meters
  • time is measured in seconds (not milliseconds)
  • vectors are 2-item arrays (for example, a velocity might be [2.3, -1.2])
  • masses are in kg
  • angles are in radians (-PI..PI], this is a more convenient scale than [0..2*PI) for ship decision logic.

o.const

As Nodewar is in alpha, these are likely to change. Currently:

  • o.const.MIN_THRUST : 0; the min thrust a ship can apply
  • o.const.MAX_THRUST : 1000; this value, in newtons, is multiplied by whatever you return in [0..1] in your thrust instructions
  • o.const.MAX_TORQUE : 1000; this value, in newton-meters, is multiplied by whatever you return in [-1..1] in your torque instructions
  • o.const.INVINCIBILITY : 0.25; how many seconds a ship is invincible after birth
  • o.const.G : 1.0; the universal gravitational constant

o.game

Info about the game state

  • o.game.time : the game time, in seconds
  • o.game.moon_field : the radius of the playing field, from the center of the field
  • o.game.center : the center of the playing field, a.k.a, [0,0].

o.mothership

o.mothership is an object (starting as {}) which is shared among all your ships and may be written to and read from at any time. This is an instantaneous method of communication. Note that modifying other elements in o is not advised and will be unreliable as we change the game's core.

When Nodewar server-side battles are implemented, your team will have its own process and there will be memory limits. Hint: it'll be plenty, maybe 100MB or so.

o.moons

An array of the moons, sorted by distance from the active ship. Currently there are 2 moons in a battle, but we could change that. Here are example expressions on the closest moon:

  • o.moons[0].pos : the closest moon's position (e.g., [0,0])
  • o.moons[0].radius : radius
  • o.moons[0].mass : mass
  • o.moons[0].vel : velocity (e.g., [0,0])
  • o.moons[0].dist : distance from the active ship
  • o.moons[0].dir : direction from the active ship, relative to the ship's sharpest vertex (-PI..PI); in other words, if dir is 0, the active ship is pointed at the moon.

o.ships

An array of all the ships in play, including dead ones, but excluding the active ship. This array is sorted by distance from the active ship, so o.ships[0] refers to the closest ship. Example expressions on the closest ship:

  • o.ships[0].friendly : bool; whether this ship is on the active ship's team
  • o.ships[0].alive : bool; whether it is alive
  • o.ships[0].queen : bool; whether it is a queen ship
  • o.ships[0].invincible : bool; whether this ship is young enough that it's invincible (or a dead shard, which also can't be broken)
  • o.ships[0].pos : position (e.g., [0,0])
  • o.ships[0].vel : velocity (e.g., [0,0])
  • o.ships[0].dist : distance from the active ship
  • o.ships[0].dir : direction from the active ship, relative to the active ship's sharpest vertex (-PI..PI); in other words, if dir is 0, the active ship is pointed directly at ships[0]
  • o.ships[0].mass : mass
  • o.ships[0].rot : the angle the ship's sharpest vertex is pointed at
  • o.ships[0].area : the size of the ship, in square meters
  • o.ships[0].area_frac : the size of the ship, relative to its first ancestor; a 1.0 is a full-sized ship, and a 0.5 is a half-area ship
  • o.ships[0].m_i : the moment of inertia of the ship (specifically along the z-axis through its centroid, which is how it is torqued)
  • o.ships[0].a_vel : the angular velocity of the ship (radians/sec)
  • o.ships[0].team_id : the team id of the ship (an integer)
  • o.ships[0].ship_id : the global id of the ship (an integer incremented when ships spawn in the game)
  • o.ships[0].age : the age of the ship

o.me

A ship entry, just like a member of o.ships, but describing the current ship. For example, o.me.queen is true if the currently active ship is queen.

o.lib

A set of helper functions for performing common vector, angle, and math operations.

o.lib.vec
.dotProduct v1, v2 returns the dot product of two vectors v1 and v2. Remembrer, vectors are just 2-item arrays in Nodewar. Positions and velocities are vectors.
.len v returns the length of a vector. For example, o.lib.vec.len(o.me.vel), would return your ship's speed.
.lenSquared v same as len, but squared; a faster calculation
.diff v1, v2 returns v1 - v2
.sum v1, v2 returns v1 + v2
.times v, n returns a vector, v, multiplied by a scalar value, n
.ang v returns the angle of a vector in (-PI...PI]
.dist v1, v2 returns the distance between two vectors. This is the same as the length of v1-v2.
.distSquared v1, v2 same as dist, but squared; a faster calculation
.normalized v returns a vector that's v / len(v). In the special case where v is [0,0], this returns [0,0].
.center arr returns the average of an array of vectors. For example, o.lib.vec.center( [o.ships[0].pos, o.ships[1].pos] ) would return the center of the 2 nearest ships.
.toPolar v returns a pair, [radius, angle], given a vector position in the gameboard.
.fromPolar p returns a vector, given a pair array ([radius, angle]) in the gameboard.
o.lib.ang
.diff a1, a2 given two angles (in radians), returns a1 - a2 in (-PI...PI]
.rescale a given an angle (in radians), returns that same angle in (-PI...PI]
.fromDegrees a given an angle (in degrees), returns that same angle in (-PI...PI]
.toDegrees a given an angle (in radians), returns that same angle in degrees [0..360)
o.lib.math
.mod a,b returns (a % b), however improved slightly for negative numbers. For example, (-2 % 5) in JavaScript returns -2, but o.lib.math.mod(-2, 5) returns 3, which might be preferable.
o.lib.physics
.speedToward v, p, dest given a ship's velocity and position, returns its speed (a scalar) towards a destination position. All 3 parameters are vectors. A simple use of this tells whether a ship is approaching another object (a positive value is returned).
o.lib.targeting
.dir ship, pos given a ship and a position, returns the angle to the position, from the ship's perspective, which is sensive to both its position and rotation. For example, a negative value would suggest the ship needs to turn in the negative direction to point at the position. Note that all ships and moons (in o.ships and o.moons) already have a .dir field filled in with respect to the active ship's perspective, so this function is only needed for (a) calculating the active ship's direction to other points of interest, or (b) calculating other ship's perspectives, in case the active ship wants to use them.
.simpleTarget ship, pos given a ship and a position, returns suggested thrust and torque values for that ship to target that position. This function is useful for beginners (and the sample ship "attack the queen" uses it), but it's not particularly smart and often misses the target. A very smart targeting system is part of your assignment in Nodewar.

that's it...for now
go ahead and play