ungroup/ungroup_game

View on GitHub
src/common/bots/Bot.cpp

Summary

Maintainability
Test Coverage
#include "Bot.hpp"

Bot::Bot(){};

/* This strategy will move towards the closest mine which has a resource the bot needs to win.
 * Once it has enough of a resource, it will move to the next closest mine with a resource it needs.
 */
std::pair<InputDef::ReliableInput, InputDef::UnreliableInput>
Bot::getGroupieMove(uint32_t bot_player_id, GameObjectController& goc) {
    auto bot_center = goc.getPlayerPosition(bot_player_id);
    auto& gc = goc.getGroupController();
    auto bot_group_id = gc.getGroupId(bot_player_id);
    auto group_ids = gc.getGroupIds();
    uint32_t nearest_joinable_group_id = -1;
    uint32_t nearest_joinable_group_dist = UINT32_MAX;

    for (auto& gid : group_ids) {
        auto& group = gc.getGroup(gid);
        if (group.isActive() && gc.getJoinable(gid) && (gid != bot_group_id)) {
            auto dist = VectorUtil::distance(bot_center, group.getCenter());
            if (dist < nearest_joinable_group_dist) {
                nearest_joinable_group_id = gid;
                nearest_joinable_group_dist = dist;
            }
        }
    }

    bool target_found = (nearest_joinable_group_id != -1);
    if (target_found) {
        auto& target_group = gc.getGroup(nearest_joinable_group_id);
        auto direction = VectorUtil::getVector(bot_center, target_group.getCenter());
        auto move = InputUtil::vectorToNearestMove(direction);
        // we only want to group with the target, so we wait to toggle joinability
        // until we're quite close to the target
        if (nearest_joinable_group_dist <
                (MIN_DISTANCE_TO_TOGGLE_JOINABLE + target_group.getRadius()) &&
            !gc.getJoinable(bot_group_id)) {
            move.first.toggle_joinable = true;
        }
        return move;
    }

    // if nothing to group with, be greedy
    return getNearestGreedyMove(bot_player_id, goc);
}

/* This strategy will move towards the closest mine which has a resource the bot needs to win.
 * Once it has enough of a resource, it will move to the next closest mine with a resource it needs.
 */
std::pair<InputDef::ReliableInput, InputDef::UnreliableInput>
Bot::getNearestGreedyMove(uint32_t bot_player_id, GameObjectController& goc) {
    auto gso = goc.getGameStateObject();
    auto bot_center = goc.getPlayerPosition(bot_player_id);
    auto resource_controller = goc.getResourceController();
    auto& player_controller = goc.getPlayerController();
    auto current_resources = resource_controller.get(bot_player_id);
    auto mines = (goc.getMineController()).getMines();

    auto bot_player = player_controller.getPlayer(bot_player_id);
    auto& bot_win_condition = bot_player->getWinCondition();
    auto resource_counts_to_win = bot_win_condition.getResourceCountsToWin();
    std::unordered_map<uint32_t, uint32_t> mine_id_to_resource_count;
    for (auto mine : mines) {
        auto mine_id = mine->getId();
        mine_id_to_resource_count[mine_id] =
            resource_controller.get(mine_id, mine->getResourceType());
    }
    std::sort(mines.begin(), mines.end(),
              [bot_center](std::shared_ptr<Mine> mine_a, std::shared_ptr<Mine> mine_b) {
                  auto dist_a = VectorUtil::distance(bot_center, mine_a->getCenter());
                  auto dist_b = VectorUtil::distance(bot_center, mine_b->getCenter());
                  return dist_a < dist_b;
              });

    for (auto& mine : mines) {
        auto curr_mine_id = mine->getId();
        if (mine_id_to_resource_count[curr_mine_id] != 0 &&
            (current_resources[mine->getResourceType()] <
             resource_counts_to_win[mine->getResourceType()])) {
            // move towards this mine since it's closest and has a resource we need
            auto direction_to_mine = VectorUtil::getVector(bot_center, mine->getCenter());
            return InputUtil::vectorToNearestMove(direction_to_mine);
        }
    }
    // if there's no point trying to mine, then move randomly.
    return getRandomMove();
}

std::pair<InputDef::ReliableInput, InputDef::UnreliableInput> Bot::getRandomMove() {
    InputDef::ReliableInput reliable_input;
    reliable_input.setAll(false);
    // When we run many random bots, if we seed them with just the time
    // then they'll all generate the same moves. XORing with the pid is
    // an easy trick for making them have different per-process seeds.
    srand(time(0) ^ getpid());
    bool up = bool(rand() % 2);
    bool down = !up ? bool(rand() % 2) : false;
    bool right = bool(rand() % 2);
    bool left = !up ? bool(rand() % 2) : false;
    bool stop = bool(rand() % 2);
    InputDef::UnreliableInput unreliable_input = {
        .toggle_up = up,
        .toggle_down = down,
        .toggle_right = right,
        .toggle_left = left,
        .toggle_stop = stop,
    };
    return std::pair<InputDef::ReliableInput, InputDef::UnreliableInput>(reliable_input,
                                                                         unreliable_input);
}

std::pair<InputDef::ReliableInput, InputDef::UnreliableInput>
Bot::getMove(uint32_t bot_player_id, BotStrategy strategy, GameObjectController& goc) {
    switch (strategy) {
        case BotStrategy::Random:
            return getRandomMove();
            break;
        case BotStrategy::NearestGreedy:
            return getNearestGreedyMove(bot_player_id, goc);
            break;
        case BotStrategy::Groupie:
            return getGroupieMove(bot_player_id, goc);
            break;
        default:
            std::cerr << "Unimplemented strategy passed to bot. Returning random move."
                      << std::endl;
            return getRandomMove();
    }
}