A Rush Strategy Agent for ABL-Wargus

From ABL
Jump to: navigation, search

This tutorial will go line-by-line through a very simple agent implementing a two-footman rush strategy using the ABL-Wargus infrastructure. Go read the Hello World! and Understanding Test Expressions tutorials if you haven't already.


Currently, this code only works with the GoW-lite.pud (Garden of War lite) map; this is only because we needed to hardcode the location of the barracks for the purpose of keeping the tutorial simple. A more sophisticated agent would use a smarter behavior for selecting the location of building sites.

Prelude

package wargusagents;

import wsm.actions.*;
import wsm.sensors.*;

behaving_entity Rush
{

This is standard stuff: a package declaration and some imports. Note that we need to import the actions and sensors this agent will be using.


Action Registration

    register act attack(int, int) with Attack;
    register act attackGround(int, int, int, boolean) with AttackGround;
    register act attackMove(int, int, int, boolean) with AttackMove;
    register act boardTransport(int, int) with BoardTransport;
    register act build(int, int, int, boolean, int) with Build;
    register act castSpell(int, int, int) with CastSpell;
    register act detonate(int, int, int, boolean) with Detonate;
    register act follow(int, int) with Follow;
    register act harvest(int, int, int, boolean) with Harvest;
    register act harvest(int, int) with Harvest;
    register act move(int, int, int, boolean) with Move;
    register act patrol(int, int, int, boolean) with Patrol;
    register act repair(int, int) with Repair;
    register act research(int, int) with Research;
    register act standGround(int, int, int, boolean) with StandGround;
    register act stop(int) with Stop;
    register act train(int, int) with Train;
    register act unloadTransport(int, int, int, boolean) with UnloadTransport;
    register act upgrade(int, int) with Upgrade;
    register act returnResources(int) with Return;

This section lets the ABL runtime know which actions are available to the agent. The signatures denote how the agent is allowed to invoke the actions and the names on the right are the classes that implement those actions. E.g. an agent can invoke harvest in two different ways; both are implemented by Harvest.java (located in wsm.actions).


Sensor and WME Registration

    register wme AssetsWME with AssetsSensor;
    register wme PlayerWME with PlayerSensor;
    register wme ResourceWME with ResourceSensor;
    register wme TerrainWME with TerrainSensor;
    register wme UnitWME with UnitSensor;
    register wme ResearchWME with ResearchSensor;

Similarly, this section lets the ABL runtime know which sensors are available to the agent, and which WMEs the corresponding information is stored in.


Utility Behaviors

    // pauses for a specified number of milliseconds
    sequential behavior Wait(int millis) {
        long finish;
        mental_act { finish = System.currentTimeMillis() + millis; }
        with ( success_test {(System.currentTimeMillis() > finish)} ) wait;
    }

    // Below are the blocking versions of the corresponding primitive acts

    sequential behavior Build(int builder, int x, int y, boolean relative, int buildeeType) {
        act build(builder, x, y, relative, buildeeType);
        // wait until the builder is idle again
        with (success_test{(UnitWME objectID==builder action::a) (a == 0 || a == 1) }) wait;
    }

    sequential behavior Train(int builder, int buildeeType) {
        act train(builder, buildeeType);
        // wait until the builder (an actual building) is not building anymore
        with (success_test{(UnitWME objectID==builder action!=3)}) wait;
    }

Note that ABL-Wargus actions are non-blocking; they simply send the command to initiate the action to the server. If we need blocking versions of those primitive actions, we need to implement them as utility behaviors, as we have done here. The utility behaviors initiate the corresponding primitive action and then wait on a condition specifiying when the action has completed. Above, we have provided some simplistic blocking behaviors. They are simplistic in the sense that they do not cover the full range of exceptional conditions that can occur; their exit conditions need refinement.

Information Freshness and Sensed WMEs

One subtle point to be aware of is that you should always use a test expression if you want the freshest information from a sensed WME (i.e. a WME that is registered with a sensor). We could have written the last utility behavior as follows:

    sequential behavior Train(int builder, int buildeeType) {
        precondition { w=(UnitWME objectID==builder) } // objectID is guaranteed to be unique
        act train(builder, buildeeType);
        // wait until the builder (an actual building) is not building anymore
        with (success_test{(w.getAction() != 3)}) wait;
    }

This is not guaranteed to work, though. The correctness of this code depends on the implementation of the sensorimotor infrastructure; in our case, UnitSensor removes old UnitWMEs from working memory and replaces them with entirely new objects. Thus, the w.getAction() would reflect out-of-date information and the success test would never succeed. If WMEs were updated in place this behavior would work, but it is not a good idea to write code that is dependent on the particulars of the implementation of the sensorimotor system.


Main Behaviors

The following are the main working behaviors. We will cover them in a bit more detail.

    sequential behavior BuildBarracks() {
        precondition { (PlayerWME barracks::BARRACKS worker::WORKER playerID::ME) 

PlayerWME is a WME that contains various pseudo-constants ("pseudo" because they depend on the race (human/orc) of the player), such as the types of all units and buildings. Here we are extracting two such constants. It also contains the player ID of the agent.

                       (UnitWME type==WORKER objectID::peasant playerID==ME) }

This part of the precondition finds a unit with type WORKER (i.e. the lone peasant that we start the game with), and binds its unique ID to a local variable. Note that there are UnitWMEs in memory for not only the agent's own units, but also for enemy and neutral units. Thus, it is necessary to check that the peasant we are grabbing actually belongs to us. This is an artifact of how information is currently represented to the agent, i.e. we have not imposed Fog of War in our sensorimotor implementation. In a more realistic implementation, we would have access to enemy unit locations only for those enemies that we can actually see.

        subgoal Build(peasant, 15, 45, false, BARRACKS);
    }

This step tells our single peasant to build a barracks at the location (15,45). The false is to indicate that the coordinates are absolute and not relative to the peasant.


The next behavior trains some footmen for our rush.

    sequential behavior TrainFootmen() {
        precondition { (PlayerWME barracks::BARRACKS soldier::SOLDIER playerID::ME) 
                       (UnitWME type==BARRACKS objectID::barracks playerID==ME) }

Similar to before, this precondition has the end effect of extracting the ID of the barracks that we just built.

        subgoal Train(barracks, SOLDIER);
        subgoal Train(barracks, SOLDIER);
    }

This trains two footmen.


The last behavior performs the actual attack logic.

    sequential behavior KillEnemyUnit() {
        precondition {(PlayerWME soldier::SOLDIER playerID::ME)
                (UnitWME objectID::enemy playerID!=ME) 

Note that enemy will represent any unit (person or building) that does not belong to the agent.

                (UnitWME objectID::soldier0 type==SOLDIER playerID==ME)
                (UnitWME objectID::soldier1 type==SOLDIER playerID==ME objectID!=soldier0) }

We need to grab the two footmen we just trained, so we find them by their type. Note that in the second test we need to explicitly find the footman (soldier1) who is not the first one (soldier0).

 
        act attack(soldier0, enemy); 
        act attack(soldier1, enemy); 
        with (success_test{(UnitWME objectID==enemy currHitPoints<=0)}) wait;
    }

This part tells the soldiers to attack the enemy unit until it is dead. Note that the condition of the success test is a little brittle; if the footmen get "distracted" and stop attacking (i.e. a peasant they were attacking disappears into a mine and comes out later), they may wait in the success test forever if the attackee does not die. The command to attack is only issued once at the beginning of the behavior.


High-Level Driver Code

The following is simply the driver code; it invokes the main behaviors.

    sequential behavior Rush() {
        subgoal BuildBarracks();
        subgoal TrainFootmen();
        with (persistent) subgoal KillEnemyUnit();
    }

This is the top-level recipe for the rush: build a barracks, train two footmen, and attack enemy units one-by-one until there are none left. Note that KillEnemyUnit only kills one unit at a time, so we have made it persistent to continue killing enemy units.

    initial_tree {
        subgoal Rush();
    }
}

Note that we can't just put all of Rush()'s steps into the initial tree; because the initial tree is a collection behavior (i.e. the steps can run in parallel), the order of their execution wouldn't be guaranteed (unless we used priority annotations).


Using Rush.abl

Compiling

% ls wargusagents
Rush.abl
% ablc wargusagents/Rush.abl
Reading from file wargusagents/Rush.abl . . .
wargusagents/Rush.abl parsed successfully.
Generating code . . .
ABL compiler took: 1.703 seconds
% javac -cp $ABLPATH:. wargusagents/*.java
Note: wargusagents/Rush_Preconditions.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

Use -g with ablc if you want to enable the ABL debugger, e.g. ablc -g wargusagents/Rush.abl

Running

  1. Run stratagus.exe
  2. Run java -cp $ABLPATH:. wsm.RunBot wargusagents.Rush ... the program will appear to hang at the prompt but is actually waiting for the game to start.
  3. Click "Single Player Game".
  4. Select the "GoW-lite.pud" scenario (click "Select Scenario").
  5. Click "Start Game".
  6. Watch the ensuing fun.