Some quick start guides, strongly based on tutorial for c++ api (https://blizzard.github.io/s2client-api):

How to Build - How to get started building this project.

Getting Started with JavaApi - How to run examples.

Tutorial 01 - Create a simple bot.

Tutorial 02 - Make your bot build SCV’s and Supply Depots.

Tutorial 03 - Building marines and scouting.

If you’re looking for the underlying protocol to communicate with the SC2 executable service see:

Important Interfaces

The two most important interfaces you will use are the ObservationInterface and the ActionInterface which are available to your S2Agent. Get the ObservationInterface by calling observation(), and the ActionInterface by calling actions() from your S2Agent.

An S2Agent is your bot, capable of performing actions in a game — a player.

An Observation is a snapshot of the game’s state at any given point in time, which is available at any time when a game or replay is running. The Observation Interface provides access to a list of units, including your Bot’s units and enemy units that are visible to your units.

An Action is a command given to units, for example to attack, move or build a structure.

Important Notes

Structures are a type of unit, and are included when you call observation().getUnits().

How to Build

  1. Set up for Java development: You will need the Java 9+ SDK, JRE, and an IDE of your choice.

  2. Since the ocraft-s2client Java API is a maven project, there is no need to download or clone the code itself. For reference, it can be found here: https://github.com/ocraft/ocraft-s2client

    <dependency>
        <groupId>com.github.ocraft</groupId>
        <artifactId>ocraft-s2client-bot</artifactId>
        <version>0.2.5</version>
    </dependency>
  3. If you want to build your own version (you need maven install on your PATH):

git clone git@github.com:ocraft/ocraft-s2client.git
cd ocraft-s2client
mvn clean install

Getting Started

This document assumes you have already built sc2api-client, if you have not follow the instructions in building section. We will go over the following.

  1. Running The Examples.

  2. Running The Unit Tests.

  3. Finding The Documentation.

  4. Building Your Own Bot.

Running The Examples

The code for examples can be found in ocraft-s2client-sample and provide some basic code on how to utilize various parts of the api.

All the examples, at a minimum, require the path to Starcraft in order to run.

On Windows and Mac, running the game manually once will automatically configure this path. On Linux, you will need to provide the path on the command line.

All other options can be configured either on the command line or in code on the S2Coordinator object.

You can view available command line arguments with the "--help" option.

java -jar ocraft-s2client-sample-0.2.3.jar --help
Usage: <main class> [-hr] [-e=<processPath>] [-m=<mapName>] [-p=<port>]
                    [-s=<stepSize>] [-t=<timeout>]
  -e, --executable=<processPath>
                            The path to StarCraft II.
  -h, --help                Display this help message.
  -m, --map=<mapName>       Which map to run.
  -p, --port=<port>         The port to make StarCraft II listen on.
  -r, --realtime[=<realtime>]
                            Whether to run StarCraft II in  real time or not.
  -s, --step_size=<stepSize>
                            How many steps to take per call.
  -t, --timeout=<timeout>   Timeout for how long the library will block for a
                              response.

Running The Unit Tests

This repo comes with a set of units and integration tests. They are located in the "src/test" folder inside of each of the project modules.

Simply building and running this project will run through all of the unit tests. Any errors will be output to standard error. For more advanced tests use maven profiles: INTEGRATION_TEST (does not need installed game) and END_TO_END_TEST (requires installed game).

Finding The Documentation

You can find documentation for the api in target/site, open index.html to view it in your browser.

The api is also documented with javadoc.

Building Your Own Bot

The sample module comes with a TutorialBot.java that is specifically stubbed out for you to follow our tutorial documents.

More details instructions are provided in Tutorial 01 through Tutorial 03. If you’d like hands on experience creating a bot please follow those!

Tutorial 1: Getting Started

This first tutorial will cover the following concepts. The source code for this tutorial will be available, in full, at the bottom of this document. We will go over the following concepts.

  1. Creating a simple bot class.

  2. Starting a bot game with the S2Coordinator class.

  3. Running the game simulation.

Creating A Simple Bot

Open the file ocraft-s2client-sample/src/main/java/com/github/ocraft/s2client/sample/TutorialBot.java, this is where you’ll be adding your code. It is already setup with the necessary include section and classpath to the api.

In order to hook into and control game state using this library you’ll want to inherit from the S2Agent class and overwrite the events you are interested in.

You can see a list of all events available to you in ClientEvents. We will start simple, our bot will start by printing hello world to console when the game starts so we will only overwrite the onGameStart event. Add the following code to TutorialBot.java.

package com.github.ocraft.s2client.sample;

import com.github.ocraft.s2client.bot.S2Agent;

public class TutorialBot {
    private static class Bot extends S2Agent {

        @Override
        public void onGameStart() {
            System.out.println("Hello world of Starcraft II bots!");
        }
    }

    public static void main(String[] args) {

    }
}

Starting The Game

Now that we have our bot the next thing to do is start a game with the bot.

Import S2Coordinator class, you will use it to start and manage a game.

Tip
The coordinator acts as a managing class for any running games and replays. Some of its responsibilities include starting the Starcraft 2 executable with the proper game settings, setting up multiplayer and stepping your bots.

The coordinator requires some preliminary information to properly start and connect to the Starcraft executable.

Replace the main method with the following code:

public static void main(String[] args) {
    Bot bot = new Bot();
    S2Coordinator s2Coordinator = S2Coordinator.setup()
            .loadSettings(args)
            .setParticipants(
                    S2Coordinator.createParticipant(Race.TERRAN, bot),
                    S2Coordinator.createComputer(Race.ZERG, Difficulty.VERY_EASY))
            .launchStarcraft()
            .startGame(BattlenetMap.of("Cloud Kingdom LE"));
    s2Coordinator.quit();
}
Important
If you have started the retail binary loadSettings should automatically load necessary settings. Otherwise you can provide the path to the game on the command line. Invoke the binary with --help to see more detailed instructions.

In a future tutorial we will cover how you can specify settings as arguments via command line.

At this point you should be able to build and run. The game should start on Cloud Kingdom LE map with you controlling the Terran against an AI zerg. You should see 'Hello world of Starcraft II bots!' print in the console when the game starts and then it will instantly shut down.

By default the game is set to single step when using the library so you must step in manually.

Running The Simulation

Now you are ready to run the Starcraft 2 simulation. Overwrite the onStep method in your bot. For now we will simply print the game loop.

Add to your bot the following function.

@Override
public void onStep() {
    System.out.println(observation().getGameLoop());
}

And the following to the bottom of the main function.

while (s2Coordinator.update()) {
}

s2Coordinator.quit();

Your bots onStep method will be called each time the coordinator steps the simulation forward. The observation() method returns an interface that contains functions to examine the current game state. We are simply printing the game loop in this example.

Exercises

As a couple simple exercises try to do the following:

  1. Print the number of minerals/vespene your bot currently has. (Hint: Use the ObservationInterface).

  2. Overwrite and utilize another event from ClientEvents. You can control Terran while the game is simulating, so you could, for example, overwrite onUnitCreated() and print the tag when an scv is made.

Full Source Code

package com.github.ocraft.s2client.sample;

import com.github.ocraft.s2client.bot.S2Agent;
import com.github.ocraft.s2client.bot.S2Coordinator;
import com.github.ocraft.s2client.protocol.game.BattlenetMap;
import com.github.ocraft.s2client.protocol.game.Difficulty;
import com.github.ocraft.s2client.protocol.game.Race;

public class TutorialBot {

    private static class Bot extends S2Agent {

        @Override
        public void onGameStart() {
            System.out.println("Hello world of Starcraft II bots!");
        }

        @Override
        public void onStep() {
            System.out.println(observation().getGameLoop());
        }

    }

    public static void main(String[] args) {
        Bot bot = new Bot();
        S2Coordinator s2Coordinator = S2Coordinator.setup()
                .loadSettings(args)
                .setParticipants(
                        S2Coordinator.createParticipant(Race.TERRAN, bot),
                        S2Coordinator.createComputer(Race.ZERG, Difficulty.VERY_EASY))
                .launchStarcraft()
                .startGame(BattlenetMap.of("Cloud Kingdom LE"));

        while (s2Coordinator.update()) {
        }

        s2Coordinator.quit();
    }
}

Tutorial 2: SCVs and Supply Depots

The goal in this tutorial will be to build scvs and supply depots.

  1. Building SCVs.

  2. Building Supply Depots.

  3. Managing idle SCVs.

As a starting point you should have the code from the end of tutorial 1 compiling and running. This tutorial will start where the last one left off.

Building SCVs

The library provides many events for your convenience, a big one we will use for this tutorial is the onUnitIdle event. This function will get called each time a unit has been built and has no orders or the unit had orders in the previous step and currently does not.

Tip
In both the Starcraft 2 engine and library both buildings and units are considered units and are represented with a Unit object.

Add the following code to your Bot class.

// In your bot class.
@Override
public void onUnitIdle(UnitInPool unitInPool) {
   Unit unit = unitInPool.unit();
   switch ((Units) unit.getType()) {
       case TERRAN_COMMAND_CENTER:
           actions().unitCommand(unit, Abilities.TRAIN_SCV, false);
           break;
       default:
           break;
   }
}

As you can see we are going to try to build an SCV with the idle unit if it is a Command Center.

Tip
The Units enum holds the ids of common units you are likely to find in 1v1 matches. Feel free to look at com.github.ocraft.s2client.protocol.data.Units to see a list of units and their corresponding id.

Building Supply Depots

Compile and run your bot now. You will see the Command Center making scvs up until the supply cap. That seems pretty good! We just need to build some supply depots now, lets replace the code in onStep with the following.

@Override
public void onStep() {
    tryBuildSupplyDepot();
}

Implement tryBuildSupplyDepot and tryBuildStructure as methods of our bot class.

private boolean tryBuildSupplyDepot() {
   // If we are not supply capped, don't build a supply depot.
   if (observation().getFoodUsed() <= observation().getFoodCap() - 2) {
       return false;
   }

   // Try and build a depot. Find a random TERRAN_SCV and give it the order.
   return tryBuildStructure(Abilities.BUILD_SUPPLY_DEPOT, Units.TERRAN_SCV);
}

private boolean tryBuildStructure(Ability abilityTypeForStructure, UnitType unitType) {
   // If a unit already is building a supply structure of this type, do nothing.
   if (!observation().getUnits(Alliance.SELF, doesBuildWith(abilityTypeForStructure)).isEmpty()) {
       return false;
   }

   // Just try a random location near the unit.
   Optional<UnitInPool> unitInPool = getRandomUnit(unitType);
   if (unitInPool.isPresent()) {
       Unit unit = unitInPool.get().unit();
       actions().unitCommand(
               unit,
               abilityTypeForStructure,
               unit.getPosition().toPoint2d().add(Point2d.of(getRandomScalar(), getRandomScalar()).mul(15.0f)),
               false);
       return true;
   } else {
       return false;
   }

}

private Predicate<UnitInPool> doesBuildWith(Ability abilityTypeForStructure) {
   return unitInPool -> unitInPool.unit()
           .getOrders()
           .stream()
           .anyMatch(unitOrder -> abilityTypeForStructure.equals(unitOrder.getAbility()));
}

private Optional<UnitInPool> getRandomUnit(UnitType unitType) {
   List<UnitInPool> units = observation().getUnits(Alliance.SELF, UnitInPool.isUnit(unitType));
   return units.isEmpty()
           ? Optional.empty()
           : Optional.of(units.get(ThreadLocalRandom.current().nextInt(units.size())));
}

private float getRandomScalar() {
   return ThreadLocalRandom.current().nextFloat() * 2 - 1;
}

Compile and run your bot now. It’s mining and building supply depots, it’s almost ready for ladder! You’ll notice when the SCV is done building a supply depot it sits idle, how useless. Lets fix that now.

Managing Idle SCVs

We have already hooked into the on idle event for building SCVs, we can use that same function to manage idle ones. Refactor your onUnitIdle function with the following.

@Override
public void onUnitIdle(UnitInPool unitInPool) {
    Unit unit = unitInPool.unit();
    switch ((Units) unit.getType()) {
        case TERRAN_COMMAND_CENTER:
            actions().unitCommand(unit, Abilities.TRAIN_SCV, false);
            break;
        case TERRAN_SCV:
            findNearestMineralPatch(unit.getPosition().toPoint2d()).ifPresent(mineralPath ->
                    actions().unitCommand(unit, Abilities.SMART, mineralPath, false));
            break;
        default:
            break;
    }
}
Tip
The ability type of SMART is equivalent to a right click in Starcraft 2 when you have a unit selected.

Now we just need to implement findNearestMineralPatch and we can fix our lazy SCV.

private Optional<Unit> findNearestMineralPatch(Point2d start) {
   List<UnitInPool> units = observation().getUnits(Alliance.NEUTRAL);
   double distance = Double.MAX_VALUE;
   Unit target = null;
   for (UnitInPool unitInPool : units) {
       Unit unit = unitInPool.unit();
       if (unit.getType().equals(Units.NEUTRAL_MINERAL_FIELD)) {
           double d = unit.getPosition().toPoint2d().distance(start);
           if (d < distance) {
               distance = d;
               target = unit;
           }
       }
   }
   return Optional.ofNullable(target);
}

Exercises

Tip
These exercises are very optional, so feel free to move onto the next tutorial. Otherwise, they act as a fun way to discover more about the API.
  1. As you build more scvs you’ll want to start building supply depots at a higher rate. Try modifying the code to build multiple supply depots instead of just 1 at a time.

  2. (Challenging) Build two refineries and start mining gas. You can use code similar to findNearestMineralPatch to find a geyser. You’ll then want to detect when the refinery is either created or becomes idle and begin gathering gas with 3 scvs.

Full Source Code

package com.github.ocraft.s2client.sample;

import com.github.ocraft.s2client.bot.S2Agent;
import com.github.ocraft.s2client.bot.S2Coordinator;
import com.github.ocraft.s2client.bot.gateway.UnitInPool;
import com.github.ocraft.s2client.protocol.data.Abilities;
import com.github.ocraft.s2client.protocol.data.Ability;
import com.github.ocraft.s2client.protocol.data.UnitType;
import com.github.ocraft.s2client.protocol.data.Units;
import com.github.ocraft.s2client.protocol.game.BattlenetMap;
import com.github.ocraft.s2client.protocol.game.Difficulty;
import com.github.ocraft.s2client.protocol.game.Race;
import com.github.ocraft.s2client.protocol.spatial.Point2d;
import com.github.ocraft.s2client.protocol.unit.Alliance;
import com.github.ocraft.s2client.protocol.unit.Unit;

import java.util.List;
import java.util.Optional;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Predicate;

public class TutorialBot {

    private static class Bot extends S2Agent {

        @Override
        public void onGameStart() {
            System.out.println("Hello world of Starcraft II bots!");
        }

        @Override
        public void onStep() {
            tryBuildSupplyDepot();
        }

        private boolean tryBuildSupplyDepot() {
            // If we are not supply capped, don't build a supply depot.
            if (observation().getFoodUsed() <= observation().getFoodCap() - 2) {
                return false;
            }

            // Try and build a depot. Find a random TERRAN_SCV and give it the order.
            return tryBuildStructure(Abilities.BUILD_SUPPLY_DEPOT, Units.TERRAN_SCV);
        }

        private boolean tryBuildStructure(Ability abilityTypeForStructure, UnitType unitType) {
            // If a unit already is building a supply structure of this type, do nothing.
            if (!observation().getUnits(Alliance.SELF, doesBuildWith(abilityTypeForStructure)).isEmpty()) {
                return false;
            }

            // Just try a random location near the unit.
            Optional<UnitInPool> unitInPool = getRandomUnit(unitType);
            if (unitInPool.isPresent()) {
                Unit unit = unitInPool.get().unit();
                actions().unitCommand(
                        unit,
                        abilityTypeForStructure,
                        unit.getPosition().toPoint2d().add(Point2d.of(getRandomScalar(), getRandomScalar()).mul(15.0f)),
                        false);
                return true;
            } else {
                return false;
            }

        }

        private Predicate<UnitInPool> doesBuildWith(Ability abilityTypeForStructure) {
            return unitInPool -> unitInPool.unit()
                    .getOrders()
                    .stream()
                    .anyMatch(unitOrder -> abilityTypeForStructure.equals(unitOrder.getAbility()));
        }

        private Optional<UnitInPool> getRandomUnit(UnitType unitType) {
            List<UnitInPool> units = observation().getUnits(Alliance.SELF, UnitInPool.isUnit(unitType));
            return units.isEmpty()
                    ? Optional.empty()
                    : Optional.of(units.get(ThreadLocalRandom.current().nextInt(units.size())));
        }

        private float getRandomScalar() {
            return ThreadLocalRandom.current().nextFloat() * 2 - 1;
        }


        @Override
        public void onUnitIdle(UnitInPool unitInPool) {
            Unit unit = unitInPool.unit();
            switch ((Units) unit.getType()) {
                case TERRAN_COMMAND_CENTER:
                    actions().unitCommand(unit, Abilities.TRAIN_SCV, false);
                    break;
                case TERRAN_SCV:
                    findNearestMineralPatch(unit.getPosition().toPoint2d()).ifPresent(mineralPath ->
                            actions().unitCommand(unit, Abilities.SMART, mineralPath, false));
                    break;
                default:
                    break;
            }
        }

        private Optional<Unit> findNearestMineralPatch(Point2d start) {
            List<UnitInPool> units = observation().getUnits(Alliance.NEUTRAL);
            double distance = Double.MAX_VALUE;
            Unit target = null;
            for (UnitInPool unitInPool : units) {
                Unit unit = unitInPool.unit();
                if (unit.getType().equals(Units.NEUTRAL_MINERAL_FIELD)) {
                    double d = unit.getPosition().toPoint2d().distance(start);
                    if (d < distance) {
                        distance = d;
                        target = unit;
                    }
                }
            }
            return Optional.ofNullable(target);
        }

    }

    public static void main(String[] args) {
        Bot bot = new Bot();
        S2Coordinator s2Coordinator = S2Coordinator.setup()
                .loadSettings(args)
                .setParticipants(
                        S2Coordinator.createParticipant(Race.TERRAN, bot),
                        S2Coordinator.createComputer(Race.ZERG, Difficulty.VERY_EASY))
                .launchStarcraft()
                .startGame(BattlenetMap.of("Cloud Kingdom LE"));

        while (s2Coordinator.update()) {
        }

        s2Coordinator.quit();
    }
}

Tutorial 3: Building Marines and Scouting

So far we’ve been focused on some of the simpler macro abilities required to make a bot. You’ve gotten your bot to build scv’s and supply depots when it’s running near the supply cap. In this tutorial we’ll learn the following.

  1. Building a Barracks.

  2. Building Marines from said Barracks.

  3. Scouting Your Opponent.

This tutorial builds on the previous so make sure the code from the last tutorial is copy pasted into TutorialBot.java and you can build/run it.

Building a Barracks

The first step to any good Terran build is to build marines, and to build marines we must first build a barracks. We’ve already seen how to construct supply depots so lets create some similar code for constructing a barracks. Modify your onStep method to the following.

@Override
public void onStep() {
    tryBuildSupplyDepot();
    tryBuildBarracks();
}
Tip
We’ll implement tryBuildBarracks shortly.

First, we have some constraints we must satisfy to build a barracks, primarily, we need a supply depot. We’d also like to only build one for this this tutorial so lets create a helper function for counting unit types and we’ll use that in tryBuildBarracks in order to determine if we should build one or not.

private int countUnitType(Units unitType) {
    return observation().getUnits(Alliance.SELF, UnitInPool.isUnit(unitType)).size();
}

That function is counting the number of a certain unit type the player owns. getUnits takes a Predicate parameter that allows you to remove units that don’t meet a certain condition. In this case that condition is that the units are of the desired UnitType.

We now have the necessary helper methods to implement tryBuildBarracks.

private boolean tryBuildBarracks() {
    if (countUnitType(Units.TERRAN_SUPPLY_DEPOT) < 1) {
        return false;
    }

    if (countUnitType(Units.TERRAN_BARRACKS) > 0) {
        return false;
    }

    return tryBuildStructure(Abilities.BUILD_BARRACKS, Units.TERRAN_SCV);
}

You can build and run your code at this point, if you’d like, you should see your bot building a barracks after it completes its first supply depot. We’d now like that barracks to actually do something. Recall we’ve overwritten a onUnitIdle event in an earlier tutorial, completion of the barracks should trigger that event!

Building Marines

Similar to how we construct SCVs we can now produce marines. Add the following code to the switch case in onUnitIdle. The entire function should look like the following, the new code is the Units.TERRAN_BARRACKS case:

@Override
public void onUnitIdle(UnitInPool unitInPool) {
   Unit unit = unitInPool.unit();
   switch ((Units) unit.getType()) {
       case TERRAN_COMMAND_CENTER:
           actions().unitCommand(unit, Abilities.TRAIN_SCV, false);
           break;
       case TERRAN_SCV:
           findNearestMineralPatch(unit.getPosition().toPoint2d()).ifPresent(mineralPath ->
                   actions().unitCommand(unit, Abilities.SMART, mineralPath, false));
           break;
       case TERRAN_BARRACKS: {
           actions().unitCommand(unit, Abilities.TRAIN_MARINE, false);
           break;
       }
       default:
           break;
   }
}

Notice how easy that is! In general, onUnitIdle is an excellent function to add code to to control unit production and orders. At this point if you build and run the code your bot should build a barracks and start producing marines with them. Our last step should be to scout the enemy.

Scouting Your Opponent

In a normal match when the game begins the minimap is pinged with all possible starting locations of enemies, the api contains that same information in the ObservationInterface. You can retrieve it via getGameInfo(). Lets use that method in our onUnitIdle so a newly spawned marine will attack move towards the enemy as soon as it’s spawned. It will be fun to see countless marines walk to their demise.

In your onUnitIdle add the following code to your switch case:

@Override
public void onUnitIdle(UnitInPool unitInPool) {
    Unit unit = unitInPool.unit();
    switch ((Units) unit.getType()) {
        case TERRAN_COMMAND_CENTER:
            actions().unitCommand(unit, Abilities.TRAIN_SCV, false);
            break;
        case TERRAN_SCV:
            findNearestMineralPatch(unit.getPosition().toPoint2d()).ifPresent(mineralPath ->
                    actions().unitCommand(unit, Abilities.SMART, mineralPath, false));
            break;
        case TERRAN_BARRACKS: {
            actions().unitCommand(unit, Abilities.TRAIN_MARINE, false);
            break;
        }
        case TERRAN_MARINE: {
            findEnemyPosition().ifPresent(point2d ->
                    actions().unitCommand(unit, Abilities.ATTACK_ATTACK, point2d, false));
            break;
        }
        default:
            break;
    }
}

// Tries to find a random location that can be pathed to on the map.
// Returns Point2d if a new, random location has been found that is pathable by the unit.
private Optional<Point2d> findEnemyPosition() {
   ResponseGameInfo gameInfo = observation().getGameInfo();

   Optional<StartRaw> startRaw = gameInfo.getStartRaw();
   if (startRaw.isPresent()) {
       Set<Point2d> startLocations = new HashSet<>(startRaw.get().getStartLocations());
       startLocations.remove(observation().getStartLocation().toPoint2d());
       if (startLocations.isEmpty()) return Optional.empty();
       return Optional.of(new ArrayList<>(startLocations)
               .get(ThreadLocalRandom.current().nextInt(startLocations.size())));
   } else {
       return Optional.empty();
   }
}

How fun, build and run and you can watch marines endlessly walk to their death.

Exercises

  1. Try building and producing marines from three barracks instead of one.

  2. (Challenging) Perform a simple rush, from your three barracks wait until you’ve gathered 10-20 marines then attack move to your enemy.

Full Source Code

package com.github.ocraft.s2client.sample;

import com.github.ocraft.s2client.bot.S2Agent;
import com.github.ocraft.s2client.bot.S2Coordinator;
import com.github.ocraft.s2client.bot.gateway.UnitInPool;
import com.github.ocraft.s2client.protocol.data.Abilities;
import com.github.ocraft.s2client.protocol.data.Ability;
import com.github.ocraft.s2client.protocol.data.UnitType;
import com.github.ocraft.s2client.protocol.data.Units;
import com.github.ocraft.s2client.protocol.game.BattlenetMap;
import com.github.ocraft.s2client.protocol.game.Difficulty;
import com.github.ocraft.s2client.protocol.game.Race;
import com.github.ocraft.s2client.protocol.game.raw.StartRaw;
import com.github.ocraft.s2client.protocol.response.ResponseGameInfo;
import com.github.ocraft.s2client.protocol.spatial.Point2d;
import com.github.ocraft.s2client.protocol.unit.Alliance;
import com.github.ocraft.s2client.protocol.unit.Unit;

import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Predicate;

public class TutorialBot {

    private static class Bot extends S2Agent {

        @Override
        public void onGameStart() {
            System.out.println("Hello world of Starcraft II bots!");
        }

        @Override
        public void onStep() {
            tryBuildSupplyDepot();
            tryBuildBarracks();
        }

        private boolean tryBuildSupplyDepot() {
            // If we are not supply capped, don't build a supply depot.
            if (observation().getFoodUsed() <= observation().getFoodCap() - 2) {
                return false;
            }

            // Try and build a depot. Find a random TERRAN_SCV and give it the order.
            return tryBuildStructure(Abilities.BUILD_SUPPLY_DEPOT, Units.TERRAN_SCV);
        }

        private boolean tryBuildStructure(Ability abilityTypeForStructure, UnitType unitType) {
            // If a unit already is building a supply structure of this type, do nothing.
            if (!observation().getUnits(Alliance.SELF, doesBuildWith(abilityTypeForStructure)).isEmpty()) {
                return false;
            }

            // Just try a random location near the unit.
            Optional<UnitInPool> unitInPool = getRandomUnit(unitType);
            if (unitInPool.isPresent()) {
                Unit unit = unitInPool.get().unit();
                actions().unitCommand(
                        unit,
                        abilityTypeForStructure,
                        unit.getPosition().toPoint2d().add(Point2d.of(getRandomScalar(), getRandomScalar()).mul(15.0f)),
                        false);
                return true;
            } else {
                return false;
            }

        }

        private Predicate<UnitInPool> doesBuildWith(Ability abilityTypeForStructure) {
            return unitInPool -> unitInPool.unit()
                    .getOrders()
                    .stream()
                    .anyMatch(unitOrder -> abilityTypeForStructure.equals(unitOrder.getAbility()));
        }

        private Optional<UnitInPool> getRandomUnit(UnitType unitType) {
            List<UnitInPool> units = observation().getUnits(Alliance.SELF, UnitInPool.isUnit(unitType));
            return units.isEmpty()
                    ? Optional.empty()
                    : Optional.of(units.get(ThreadLocalRandom.current().nextInt(units.size())));
        }

        private float getRandomScalar() {
            return ThreadLocalRandom.current().nextFloat() * 2 - 1;
        }

        @Override
        public void onUnitIdle(UnitInPool unitInPool) {
            Unit unit = unitInPool.unit();
            switch ((Units) unit.getType()) {
                case TERRAN_COMMAND_CENTER:
                    actions().unitCommand(unit, Abilities.TRAIN_SCV, false);
                    break;
                case TERRAN_SCV:
                    findNearestMineralPatch(unit.getPosition().toPoint2d()).ifPresent(mineralPath ->
                            actions().unitCommand(unit, Abilities.SMART, mineralPath, false));
                    break;
                case TERRAN_BARRACKS:
                    actions().unitCommand(unit, Abilities.TRAIN_MARINE, false);
                    break;
                case TERRAN_MARINE:
                    findEnemyPosition().ifPresent(point2d ->
                            actions().unitCommand(unit, Abilities.ATTACK_ATTACK, point2d, false));
                    break;
                default:
                    break;
            }
        }

        private Optional<Unit> findNearestMineralPatch(Point2d start) {
            List<UnitInPool> units = observation().getUnits(Alliance.NEUTRAL);
            double distance = Double.MAX_VALUE;
            Unit target = null;
            for (UnitInPool unitInPool : units) {
                Unit unit = unitInPool.unit();
                if (unit.getType().equals(Units.NEUTRAL_MINERAL_FIELD)) {
                    double d = unit.getPosition().toPoint2d().distance(start);
                    if (d < distance) {
                        distance = d;
                        target = unit;
                    }
                }
            }
            return Optional.ofNullable(target);
        }

        private boolean tryBuildBarracks() {
            if (countUnitType(Units.TERRAN_SUPPLY_DEPOT) < 1) {
                return false;
            }

            if (countUnitType(Units.TERRAN_BARRACKS) > 0) {
                return false;
            }

            return tryBuildStructure(Abilities.BUILD_BARRACKS, Units.TERRAN_SCV);
        }

        private int countUnitType(Units unitType) {
            return observation().getUnits(Alliance.SELF, UnitInPool.isUnit(unitType)).size();
        }

        // Tries to find a random location that can be pathed to on the map.
        // Returns Point2d if a new, random location has been found that is pathable by the unit.
        private Optional<Point2d> findEnemyPosition() {
            ResponseGameInfo gameInfo = observation().getGameInfo();

            Optional<StartRaw> startRaw = gameInfo.getStartRaw();
            if (startRaw.isPresent()) {
                Set<Point2d> startLocations = new HashSet<>(startRaw.get().getStartLocations());
                startLocations.remove(observation().getStartLocation().toPoint2d());
                if (startLocations.isEmpty()) return Optional.empty();
                return Optional.of(new ArrayList<>(startLocations)
                        .get(ThreadLocalRandom.current().nextInt(startLocations.size())));
            } else {
                return Optional.empty();
            }
        }
    }

    public static void main(String[] args) {
        Bot bot = new Bot();
        S2Coordinator s2Coordinator = S2Coordinator.setup()
                .loadSettings(args)
                .setParticipants(
                        S2Coordinator.createParticipant(Race.TERRAN, bot),
                        S2Coordinator.createComputer(Race.ZERG, Difficulty.VERY_EASY))
                .launchStarcraft()
                .startGame(BattlenetMap.of("Cloud Kingdom LE"));

        while (s2Coordinator.update()) {
        }

        s2Coordinator.quit();
    }
}