Here’s the bit that made the magic happen in my bot.
public PlayerCommand computeNextPlayerAction(GameState gameState) {
List<StrategicObjective> strategicObjectives = strategicDecisionLayer.generatePrioritizedStrategicObjectives(gameState);
UnitAllocation unitAllocation = tacticalDecisionLayer.generateTacticalAllocation(gameState, strategicObjectives);
List<CommandAction> actions = operationalDecisionLayer.executeActions(gameState, unitAllocation);
PlayerCommand playerCommand = new PlayerCommand();
playerCommand.setPlayerID(botId);
playerCommand.setActions(actions);
return playerCommand;
}
The first layer was quite rudimentary, doing stuff like this.
public List<StrategicObjective> generatePrioritizedStrategicObjectives(GameState gameState) {
List<StrategicObjective> strategicObjectives = new ArrayList<>();
strategicObjectives.add(createFoodObjective(gameState));
strategicObjectives.add(createWoodObjective(gameState));
strategicObjectives.add(createHeatObjective(gameState));
strategicObjectives.add(createStoneObjective(gameState));
strategicObjectives.add(createGoldObjective(gameState));
strategicObjectives.add(createBuildingObjective(gameState));
strategicObjectives.add(createClaimLandObjective(gameState));
Collections.sort(strategicObjectives);
return strategicObjectives;
}
private FoodObjective createFoodObjective(GameState gameState) {
FoodObjective foodObjective = new FoodObjective(gameState);
double importance = 1.05;
if (gameState.getFood() >= gameState.getPopulation() * 6) {
importance -= 0.75;
}
if (gameState.getFood() < gameState.getHeat()) {
importance += 0.1;
}
foodObjective.setImportance(importance);
return foodObjective;
The tactical layer populated an object that tracks a counter of available units and units allocated to specific actions. It determines the minimum number of units required for a particular action, and if units are left over, allocates them to the highest priority actions.
@Override
public void allocateRequiredUnits(UnitAllocation unitAllocation) {
double foodGap = foodTarget - gameState.getFood();
if (shouldScout(unitAllocation)) {
unitAllocation.addScoutUnits(1);
}
if (foodGap > 0 && canGather()) {
int foodUnitCapacityRemaining = gameState.getRemainingFoodGatheringUnitCapacity();
int requiredUnits = (int) Math.ceil(foodGap / EXPECTED_FOOD_PER_UNIT);
if (requiredUnits > foodUnitCapacityRemaining) {
requiredUnits = foodUnitCapacityRemaining;
}
int unitsToAllocate = unitAllocation.getMaxAvailableUnits(requiredUnits);
unitAllocation.addFarmUnits(unitsToAllocate);
}
}
@Override
public void allocateReserveUnits(UnitAllocation unitAllocation) {
int currentTier = gameState.getCurrentTierLevel();
double gapToMax = (Bot.MAX_FOOD_BY_TIER[currentTier]) - gameState.getFood();
if (gapToMax > 0 && canGather()) {
int maxUnits = (int) Math.ceil(gapToMax / EXPECTED_FOOD_PER_UNIT);
int unitsToAllocate = unitAllocation.getMaxAvailableUnits(maxUnits);
unitAllocation.addFarmUnits(unitsToAllocate);
}
}
Finally, the operational layer tries to find the best way to use the allocated units for each action. For resources, it looks at the distance, reward, and travel time to determine the highest “per tick” yield.
public List<CommandAction> executeActions(GameState gameState, UnitAllocation unitAllocation) {
List<CommandAction> actions = new ArrayList<>();
if (unitAllocation.getFarmUnits() > 0) {
actions.addAll(gather(gameState, unitAllocation.getFarmUnits(), ResourceType.FOOD));
}
if (unitAllocation.getLumberUnits() > 0) {
actions.addAll(gather(gameState, unitAllocation.getLumberUnits(), ResourceType.WOOD));
}
Did the rest of you try and keep your population under control or just expand blindly? I think my bot’s downfall was in growing its population so quickly that it ran out of resources and could no longer sustain it. I saw it shoot up to the top a few times, stay there for a while, and then start losing score rapidly. It worked well when testing against previous versions of itself, but it’s always been super aggressive - so as long as the latest version could stay ahead of the previous versions when the population started crashing, it would do okay.