Skip to content

Latest commit

History

24 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

CodeCraft Tutorial Join the chat at https://gitter.im/cswinter/CodeCraftGameMaven Central

This is a tutorial which describes how to implement an AI for the programming game CodeCraft. You "play" CodeCraft by writing a program in Scala or Java and I am going to assume that you are already familiar with one of these languages. If you encounter a bug, have a question, or want to give feedback you can go on Gitter or send me an email at codecraftgame@gmail.com.

Basics

To get things set up, you need to create a new project and add the CodeCraft library as a dependency (in sbt: libraryDependencies += "org.codecraftgame" % "codecraft_2.11" % "0.6.0"). An implementation of the setup described in this section can be found in the folders scala-template and java-template.

The main entry point into the game is the TheGameMaster object. You can start the first level by calling TheGameMaster.runLevel1 as such:

importcwinter.codecraft.core.api._objectMain {
defmain(args: Array[String]):Unit= {
TheGameMaster.runLevel1(newMothership)
}
}

The same in Java:

importcwinter.codecraft.core.api.TheGameMaster;
classMain {
publicstaticvoidmain(String[] args) {
TheGameMaster.runLevel1(newMothership());
}
}

The runLevel1 method expects an object of type DroneController as argument. This object will implement the AI for your initial unit. So let's create a new file to implement this class:

importcwinter.codecraft.core.api._classMothershipextendsDroneController {
}

For Java, use the JDroneController variant instead. It is essentially identical to DroneController, but its getters return Java collections rather than their Scala counterparts:

importcwinter.codecraft.core.api.*;
classMothershipextendsJDroneController {
}

As you can see, the Mothership class doesn't contain any code so your unit AI won't do anything yet. However, you should now be able to run the program. If everything works correctly, you will see a window that displays the game world. The camera is initially centered on your mothership, which looks like a septagon with various shapes inside (its modules, more on that later). The green pentagons are mineral crystals, which can be harvested to gain resources and construct additional drones. You can move your camera using the WASD keys and zoom in/out with Q and E. Somewhere to the left of your mothership you will find your orange opponent, who is already busy plotting your downfall.

Building a scouting drone

Ok, so let's actually tell our mothership to do something. The first thing we want is to build a new drone that will scout the map, harvest minerals and bring them back to the mothership. To do so, we override the DroneController.onSpawn method inside the Mothership class. This method is called automatically by the game engine when our drone first spawns. Inside onSpawn, we call buildDrone(controller, droneSpec). This method take two arguments:

  • controller is another DroneController that will govern the behaviour of our new Drone.
  • droneSpec is a DroneSpec object which specifies what modules our new drone will have. (in this case, we want two storage modules which will allow us to harvest and transport resources)

In Scala, you can use a simpler variant of buildDrone that allows you to directly specify with modules using named parameters instead of creating a DroneSpec object. Add the following code to your Mothership class:

overridedefonSpawn():Unit= buildDrone(newHarvester, storageModules =2)
@OverridepublicvoidonSpawn() {
DroneSpecharvesterSpec = newDroneSpec().withStorageModules(2);
buildDrone(newHarvester(), harvesterSpec);
}

Of course we still need to implement the Harvester, so create a new file with the following contents:

importcwinter.codecraft.core.api._importcwinter.codecraft.util.maths.Vector2importscala.util.RandomclassHarvesterextendsDroneController {
overridedefonTick():Unit= {
if (!isMoving) {
valrandomDirection=Vector2(2* math.Pi*Random.nextDouble())
valtargetPosition= position +500* randomDirection
moveTo(targetPosition)
}
}
}
importcwinter.codecraft.core.api.*;
importcwinter.codecraft.util.maths.Vector2;
importjava.util.Random;
classHarvesterextendsJDroneController {
staticRandomrng = newRandom();
@OverridepublicvoidonTick() {
if (!isMoving()) {
Vector2randomDirection = newVector2(2 * Math.PI * rng.nextDouble());
Vector2targetPosition = position.plus(randomDirection.times(500));
moveTo(targetPosition);
}
}
}

This time, we override the onTick method which is called on every timestep. First, we test whether the drone is currently moving using the isMoving property. If this is not the case, we give the drone a command to move into a new random direction for 100 timesteps using the moveInDirection method. You should now run the program again and verify that your mothership constructs a new drone which moves randomly across the map.

Harvesting resources

We still want to harvest resources and return them to the mothership. For this, we override the onMineralEntersVision method which is called whenever a mineral crystal enters the sight radius of our drone. When this happens, we want to stop scouting and move towards the mineral crystal. Once we have arrived, the onArrivesAtMineral method is called, where we give orders to harvest the mineral. We also modify our code in the onTick method to send the drone back to the mothership when it's storage is full. Once the drone arrives there, the onArrivesAtDrone method will be called where we give orders to deposit the mineral crystals. The HarvesterController class should now look like this:

classHarvester(mothership: DroneController) extendsDroneController {
overridedefonTick():Unit= {
if (!isMoving &&!isHarvesting) {
if (availableStorage ==0) moveTo(mothership)
else {
valrandomDirection=Vector2(2* math.Pi*Random.nextDouble())
valtargetPosition= position +500* randomDirection
moveTo(targetPosition)
}
}
}
overridedefonMineralEntersVision(mineral: MineralCrystal) =if (availableStorage >0) moveTo(mineral)
overridedefonArrivesAtMineral(mineral: MineralCrystal) = harvest(mineral)
overridedefonArrivesAtDrone(drone: Drone) = giveResourcesTo(drone)
}
classHarvesterextendsJDroneController {
staticRandomrng = newRandom();
privateJDroneControllermothership;
publicHarvester(JDroneControllermothership) {
this.mothership = mothership;
}
@OverridepublicvoidonTick() {
if (!isMoving() && !isHarvesting()) {
if (availableStorage() == 0) moveTo(mothership);
else {
Vector2randomDirection = Vector2(2 * Math.PI * rng.nextDouble());
Vector2targetPosition = position.plus(randomDirection.times(500));
moveTo(targetPosition);
}
}
}
@OverridepublicvoidonMineralEntersVision(MineralCrystalmineral) {
if (availableStorage() > 0) moveTo(mineral);
}
@OverridepublicvoidonArrivesAtMineral(MineralCrystalmineral) {
harvest(mineral);
}
@OverridepublicvoidonArrivesAtDrone(Dronedrone) {
giveResourcesTo(drone);
}
}

We also need to make a small change to the Mothership controller. The HarvesterController now expects a reference to the mothership as argument, so it knows where to return the minerals. We are also going to move the construction code into the onTick method so that we keep building new drones. The Mothership class now looks like this:

classMothershipextendsDroneController {
overridedefonTick():Unit=if (!isConstructing) buildDrone(newHarvester(this), storageModules =2)
}
importcwinter.codecraft.core.api.*;
classMothershipextendsJDroneController {
staticfinalDroneSpecHARVESTER_SPEC = newDroneSpec().withStorageModules(2);
@OverridepublicvoidonTick() {
if (!isConstructing()) {
buildDrone(newHarvester(this), HARVESTER_SPEC);
}
}
}

If you run your program again now, you should see a growing armada of harvesters collecting all the mineral crystals.

Combat

Now that you have laid the economic foundations for your drone empire, it is time to start thinking about how to beat your opponent. By now you probably got the hang of how CodeCraft works, so I will just give you a high level overview of how to implement your army. After you have built some harvesters, you will want to start production on a different type of drone. Instead of storageModules, it should be equipped with one or more missileBatteries and maybe even shieldGenerators. Another controller will be required as well, and you will find the following methods useful:

  • The DroneController method dronesInSight returns a Set of all Drones which can be seen by this drone controller
  • The Drone class has a method isEnemy which tells you whether that drone is an enemy
  • The DroneController method isInMissileRange(target: Drone) can be used to check whether some drone is within the range of your missiles
  • The DroneController method fireMissilesAt(target: Drone) will fire all your missiles at the drone target
  • If you are using Java method parentheses aren't optional, so you will need to write e.g. dronesInSight() and isEnemy()

If you don't quite manage to get all of this to work, you can check out the scala-solution and java-solution directories in this repo, which contain a full implementation of everything described in this tutorial. If you want to go even further, check out the next section which gives an overview of all the other parts of the API which haven't been covered yet. If you write an AI that uses a different strategy than those in the current levels (or which beats all levels), please send me a link to your code at codecraft@gmail.com and I'll include in the next release.

What to do next

Hopefully this tutorial has succeded in giving you a good understanding of what CodeCraft is about and how to use it. If you want to continue building out your AI, you can get answers to any questions you have from the comprehensive documentation or fellow CodeCraft users. In addtiion to that, this section gives a quick overview of the most useful parts of the API.

TheGameMaster

To configure and start the game, you use TheGameMaster object. TheGameMaster has methods runLevel1, runLevel2, ..., runLevel7 which take as an argument the DroneController for your mothership and start the corresponding level. There is also a runGame method which allows you to start a game with two custom DroneControllers. The game automatically records replays of all games in the folder ~/.codecraft/replays. You can run the last recorded replay using runLastReplay and run the replay with a specific filename using runReplay.

Multiplayer

You can start a multiplayer game using TheGameMaster.prepareMultiplayerGame. This returns a Future[DroneSimulator] which completes as soon as another player connects to the same server:

importcwinter.codecraft.core.api._importscala.concurrent.Awaitimportscala.concurrent.duration._objectMain {
defmain(args: Array[String]):Unit= {
valgame=Await.result(
TheGameMaster.prepareMultiplayerGame("SERVER ADDRESS", newMothership), 10.minutes)
TheGameMaster.run(game)
}
}

You can run a server by calling cwinter.codecraft.core.multiplayer.Server.start().

DroneController

Almost all interactions with the game world goes through this class. If you are using Java, you should use the JDroneController class instead, which is almost identical but returns Java collections rather than their Scala counterparts. DroneController has three different kinds of methods:

  • Event handlers such as onSpawn and onDroneEntersVision. These are automatically called by the game on specific events and you can override them to respond to these events.
  • Commands such as moveTo and buildDrone. You can call these methods to make your drones perform various actions.
  • Properties such as position and hitpoints which allow you to query the current state of the drone.

You can find a complete description of all of them in the API reference. In some methods (e.g. onDroneEntersVision) you are given a Drone object. Since this could reference an enemy drone, it only exposes a subset of the properties and none of the event and command methods.

Vector2

Vector2 is an immutable 2D vector and used throughout CodeCraft. It defines various methods and operators to perform e.g. vector addition, scalar multiplication and compute it's length. Details can be found in the API reference.

Debug

You can display a string at any position in the game world by using the DroneController.showText(text: String, position: Vector2) method. This is only valid for one timestep, so you if you will need to call this method on every timestep on which you want the text to be displayed. E.g. if you wanted your drones to display their position, you could use this code:

override def onTick(): Unit = {
showText(position.toString, position)
}

About

Tutorial for the CodeCraft programming game.

Resources

Stars

18 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages