📜 ⬆️ ⬇️

Start in Google AI Challenge in Java

I have long been interested in the topic of programming the behavior of objects in the virtual world. But practical knowledge in this area leaves much to be desired, so I recently began to look for a small project for investing. In the end, I found it, grateful to Google and ideas4ru for the announcement.

Language and starter pack


Project organizers offer more than a dozen languages ​​for bot development, I chose Java. I can’t say that I know Java well, but it happened (C ++ is verbose, PHP is not cool, C # is too soft, JavaScript is a long time to deal with V8, Lua doesn’t like the syntax, Pascal is too much school, Python - a little scary). Java starter pack can be downloaded from the official site or via this direct link .

How i started


Maybe I’m a brake or I don’t know Java well, but at the beginning I couldn’t understand how the code works in the main class:
public void doTurn() { Ants ants = getAnts(); for (Tile myAnt : ants.getMyAnts()) { for (Aim direction : Aim.values()) { if (ants.getIlk(myAnt, direction).isPassable()) { ants.issueOrder(myAnt, direction); break; } } } } 

It took me about an hour to sort through the bones of the entire code for the starter pack (I advise everyone to do the same, and not to read articles on how to get started quickly) As a result, it turned out that the first cycle runs through the cells with friendly ants, while the second one tries to move the ant in one of four directions (successive enumeration of the Aim enumeration).

At the very first step, I encountered one very nice rule - when moving two ants one cell, they both die. To solve this problem, an Ants class has been edited.
')
I added a list of busy cells:
 public class Ants { private final Set<Tile> employed = new HashSet<Tile>(); //... 

When cleaning the positions of friendly ants, added a purge of occupied positions:
 public void clearMyAnts() { employed.clear(); //... 

And, finally, almost completely rewrote the issueOrder method:
 public boolean issueOrder(Tile myAnt, Aim direction) { Order order = new Order(myAnt, direction); Tile newPosition = getTile(myAnt, direction); if(getIlk(myAnt, direction).isPassable() && !employed.contains(newPosition)) { orders.add(order); //     ,     employed.add(newPosition); System.out.println(order); System.out.flush(); return true; } return false; } 

The code in the main class will decrease slightly:
 public void doTurn() { Ants ants = getAnts(); for (Tile myAnt : ants.getMyAnts()) { for (Aim direction : Aim.values()) { if (ants.issueOrder(myAnt, direction)) break; } } } 

A bit of individuality


We dream a little and imagine that in the future each of our ant will be an individual and possess some parameters. The continuation of such thoughts will be the class Ant:
 public class Ant { protected Ants ants; protected Tile tile; protected int waitStep = 0; public Ant(Ants ants, Tile tile) { this.tile = tile; this.ants = ants; } public Tile getTile() { return tile; } //      (   !) public boolean issueOrder(Aim direction) { if(ants.issueOrder(tile, direction)) { tile = ants.getTile(tile, direction); return true; } else return false; } //    ,      public boolean waiting() { return waitStep-- > 0; } //  public void think() { for(Aim direction : Aim.values()) { if(ants.issueOrder(direction)) break; } } } 

Next you need to make a lot of small changes to this class began to work. It's pretty boring and monotonous, but still ... let's start with the MyBot method:
  public void doTurn() { getAnts().think(); //     } 

Add the missing method to Ants and change the content type of the myAnts list:
  private final Set<Ant> myAnts = new HashSet<Ant>(); //... public void think() { for(Ant ant : myAnts) { if(ant.waiting()) // ,    continue; ant.think(); //    } } 

Now you need to carefully monitor the addition and removal of ant objects. Those who understood the code were most likely seen in the Ants class update method, in which you can track the appearance of a friendly ant and death (but death is common to all ants). Such a common death can be corrected in several ways ... I decided to add two types of death.

We make changes to the class-listing Ilk:
 public enum Ilk { MY_DEAD, ENEMY_DEAD, //  DEAD, //... public boolean isUnoccupied() { return this == LAND || this == MY_DEAD || this == ENEMY_DEAD; } } 

Let's update the removeAnt method in the Bot class:
  public void removeAnt(int row, int col, int owner) { ants.update(owner > 0 ? Ilk.ENEMY_DEAD : Ilk.MY_DEAD, new Tile(row, col)); } 

Now everything is ready to make an "observation" of the ants, we rule the update method in the class Atns:
  public void update(Ilk ilk, Tile tile) { map[tile.getRow()][tile.getCol()] = ilk; switch (ilk) { case FOOD: foodTiles.add(tile); break; case MY_ANT: myAnts.add(new Ant(this, tile)); break; case ENEMY_ANT: enemyAnts.add(tile); break; case MY_DEAD: myAnts.remove(new Ant(this, tile)); } } 

Food and bullshit - a little brain


Further, it makes no sense to try to paint all the steps, just give the code with comments. Modify ant behavior by modifying Ant code:
 public class Ant { //... public void think() { if(!doFood()) //   , doRandom(); //   } protected boolean doRandom() { while(!issueOrder(Aim.getRandom())); //     return true; } protected boolean doFood() { return moveToObject(ants.getFoodTiles()); } //        protected boolean moveToObject(Set<Tile> objects) { Tile object = findObject(objects); if(object != null) { issueOrder(ants.getDirections(object, tile).get(0)); return true; } return false; } //       protected Tile findObject(Set<Tile> objects) { Tile find = null; int distance = 0; int minDistance = ants.MAX_MAP_SIZE; for(Tile aspt : objects) { distance = ants.getDistance(aspt, tile); if(minDistance > distance) { find = aspt; minDistance = distance; } } return find; } } 

When writing this code, I added a static method to the Aim enumeration class that returns a random direction:
 public enum Aim { private static final Random random = new Random(); //... public static Aim getRandom() { return values()[random.nextInt(values().length)]; } } 

Mistake


Maybe I messed up somewhere in the code, but it seems that the error in the Ants class getDirections method crept in:
  public List<Aim> getDirections(Tile t1, Tile t2) { List<Aim> directions = new ArrayList<Aim>(); if (t1.getRow() < t2.getRow()) { if (t2.getRow() - t1.getRow() >= rows / 2) { directions.add(Aim.SOUTH); // Aim.NORTH } else { directions.add(Aim.NORTH); // Aim.SOUTH } } else if (t1.getRow() > t2.getRow()) { if (t1.getRow() - t2.getRow() >= rows / 2) { directions.add(Aim.NORTH); //   ... } else { directions.add(Aim.SOUTH); //...    (  ) } } if (t1.getCol() < t2.getCol()) { if (t2.getCol() - t1.getCol() >= cols / 2) { directions.add(Aim.EAST); //   ... } else { directions.add(Aim.WEST); //...    (  ) } } else if (t1.getCol() > t2.getCol()) { if (t1.getCol() - t2.getCol() >= cols / 2) { directions.add(Aim.WEST); //   ... } else { directions.add(Aim.EAST); //...    (  ) } } return directions; } 

For a long time I could not understand what was wrong ... the ant was looking for food somehow wrong ... it turned out to be the case.

Conclusion


I do not think that in such a game you need self-study. Perhaps it would be better to have a set of strategies + a small adjustment (group size, current strategy ... I’ll not reveal the remaining parameters yet). I'm not sure yet how effective such ants will be, but I will try to write about it in the second article.

PS Self-study is not necessary, because it is a closed system in which new factors do not appear. In the meantime, self-learning opponent learns, you can capture the enemy hills :)

Source: https://habr.com/ru/post/130722/


All Articles