Предыдущие части:
Скачайте архив или сделайте pull
(тэг v.1.3
), чтобы получить актуальную версию исходников.
Изменения следующие:
NullBotAI
(см. Паттерн Null Object)Задача для защищающихся Ботов заключается в том, чтобы предотвратить столкновение с атакующими. Проще всего разбить эту задачу на две части: сначала оценить степень опасности каждой области игрового мира; а затем выбрать оптимальный в сложившейся ситуации ход.
Зона вокруг атакующего Бота - опасна, а чем ближе мы к нему, тем опасность серьезнее. На основе этого логичного предположения не сложно построить "Карту Опасности". Для этого поставим каждой клетке игрового мира некий балл. Чем выше балл, тем опаснее клетка:
class AIModel {
public:
// …
// Клетка с атакующим Ботом = смерть
static const int DEATH_RADIUS = 0;
// Клетки вокруг атакующего Бота = сверх опасно
static const int EXTREME_DANGER_RADIUS = 1;
// Область в двух клетках от атакующего Бота = нужно быть осторожным
static const int WARNING_RADIUS = 2;
// Очки для различных уровней опасности (выбраны достаточно произвольно)
enum DangerLevel {
WARNING = 10,
CRITICAL = 30,
EXTREME = 50,
DEATH = 100
};
// …
};
void AIModel::refreshDangerMap() {
for( Row& r : m_dangerMap ) {
std::fill( r.begin(), r.end(), 0 );
}
for( auto b : m_bots ) {
if( b->getType() != 2 ) {
// Нас интересуют только атакующие Боты
continue;
}
int x = b->getX() / BLOCK_SIZE;
int y = b->getY() / BLOCK_SIZE;
markDangerArea( x, y, DEATH_RADIUS, DEATH );
markDangerArea( x, y, EXTREME_DANGER_RADIUS, EXTREME );
markDangerArea( x, y, WARNING_RADIUS, WARNING );
}
}
void AIModel::markDangerArea( int x, int y, int radius, int score ) {
for( int row = y - radius; row <= y + radius; ++row ) {
for( int column = x - radius; column <= x + radius; ++column ) {
if(
column < 0 || m_field[ 0 ].size() <= static_cast< size_t >( column ) ||
row < 0 || m_field.size() <= static_cast< size_t >( row )
) {
continue;
}
m_dangerMap[ row ][ column ] += score;
}
}
}
Для большей наглядности все опасные клетки подсвечиваются в Представлении на основании их уровня (см. скриншот выше).
Имея Карту Опасности, защищающийся Бот может сориентироваться, в какую сторону лучше идти. Если у него есть несколько допустимых направлений, то интуитивно понятно, что лучше всего выбирать более безопасное (хотя это и не всегда работает правильно). За основу возьмем простой ИИИ SingleMemRandomAI
, который обеспечивал достаточно естественную траекторию движения:
// Уровень опасности будем оценивать по крайним точкам на основе ожидаемого положения Бота после хода
// Если ход сделан налево, то проверяем соседнюю клетку слева
// Если ход сделан вверх. то проверяем соседнюю клетку сверху
// И т.д.
int getDanger( Bot::Direction dir, const Bot& b, const AIModel& model ) {
const int halfBotSize = b.getSize() / 2;
Bot bCopy = b;
bCopy.setDirection( dir );
bCopy = model.doMove( bCopy );
int x = bCopy.getX();
int y = bCopy.getY();
switch( dir ) {
case Bot::LEFT:
x -= halfBotSize;
break;
case Bot::RIGHT:
x += halfBotSize;
if( x % AIModel::BLOCK_SIZE == 0 ) {
x -= AIModel::BLOCK_SIZE;
}
break;
case Bot::UP:
y -= halfBotSize;
break;
case Bot::DOWN:
y += halfBotSize;
if( y % AIModel::BLOCK_SIZE == 0 ) {
y -= AIModel::BLOCK_SIZE;
}
break;
default:
break;
}
return model.getDangerMap()[ y / AIModel::BLOCK_SIZE ][ x / AIModel::BLOCK_SIZE ];
}
void RunAwayAI::doStep( const AIModel& model, Bot* bot ) {
bot->startMoving();
auto directions = model.findValidDirections( *bot );
if( !directions.empty() ) {
// Сначала находим минимальный уровень опасности в соседних клетках для допустимых направлений
auto it = directions.begin();
int minDanger = getDanger( *it, *bot, model );
++it;
for( ; it != directions.end(); ++it ) {
int danger = getDanger( *it, *bot, model );
if( danger < minDanger ) {
minDanger = danger;
}
}
// Исключаем из допустимых все направления, уровень опасности для которых превышает минимум
// Этим мы гарантируем, что заведомо более опасные пути выбираться не будут
it = directions.begin();
while( it != directions.end() ) {
if( minDanger < getDanger( *it, *bot, model ) ) {
it = directions.erase( it );
} else {
++it;
}
}
}
// А здесь то же самое, что и для SingleMemRandomAI
if(
!directions.empty() &&
std::find( directions.begin(), directions.end(), bot->getDirection() ) == directions.end()
) {
bot->setDirection( directions[ rand() % directions.size() ] );
}
}
Алгоритм достаточно прост, но и работает не идеально (идеальный бы не допускал столкновений). Однако следует учитывать, что защищающиеся Боты находятся в заведомо проигрышной ситуации, поэтому получившееся решение смотрится вполне приемлемым.
ИИИ поиска и убегания работают, но для них наблюдаются некоторые проблемы, которые относительно несложно исправить:
В качестве темы следующей статьи у меня есть несколько вариантов:
Пишите в комментариях, что бы вы хотели увидеть. Конкретные постановки задач приветствуются.
Анон:
Так держать, мне нравиться!
Спасибо :)
Анон
Так держать, мне нравиться!