Игра написана на языке программирования Lua, поэтому и примеры кода я буду приводить на этом языке.
Опишу некоторые детали игры, важные для ИИ:
- Игра – пошаговая стратегия. Вначале ходит игрок, потом ИИ делает свои действия за каждую страну. ИИ работает только при нажатии Следующий ход и понятия не имеет, что происходит в другое время.
- В игре есть карта, на которой можно рекрутировать/перемещать/распускать войска. ИИ должен анализировать ее и принимать необходимые решения.
- В игре можно заключать мир/объявлять войну/подписывать пакт о ненападении/заключать и расторгать союзы. ИИ должен уметь справляться и с этим.
- Технологии и политические институты доступны только игроку. У ИИ бонусы не меняются с начала игры, в отличие от игрока.
Функция, которая отвечает за начало следующего хода выглядит так:
function next_step()
--Обрабатываем перемещения игрока на карте
--ИИ отвечает на предложения, поступившие к нему
for k, v in pairs(game_data.lands) do
calc_ai(k)
end
--Все остальное
End
Вы, должно быть, заметили волшебную функция calc_ai
, которая и отвечает за все действия ботов. Еще можно увидеть одну интересную деталь: функция выполняется поочередно за каждую страну.
Теперь ответим на вопрос, что же происходит в calc_ai
.
function calc_ai(land)
if land == game_data.player_land or land == "Undeveloped_land"
or not game_data.lands[land] then
return
end
--ИИ разбирается с кем воевать, а с кем дружить
for k, v in pairs(game_data.lands) do
local option = math.random()
--Устанавливаем данные ИИ, отличающиеся для взаимодействия с ботом и игроком.
if k == game_data.player_land then
ai_data = fixed_ai_data[game_data.difficulty].player
else
ai_data = fixed_ai_data[game_data.difficulty]
end
end
move_army( math.random(), land) --Перемещаем армию
balance_army( math.random(), land) -- Балансируем армию
end
Пока мы не будем глубоко разбираться, как ИИ принимает решения в политике, а заметим следующее:
- Cуществует проверка, чтобы ИИ случайно не пошел за игрока или за непонятное Undeveloped_land. Это просто земля без страны(На самом деле у провинции всегда должна быть страна, владеющая ей, поэтому Undeveloped_land – это обозначение в первую очередь для игры).
- ИИ выбирает разные таблицы для взаимодействия со страной в зависимости от того, игрок ей управляет или нет.
- ИИ вначале перемещает армию, и лишь потом нанимает.
fixed_ai_data
– сама таблица. Названа так из-за того, что она не меняется (точнее сказать, что переменная ссылается всегда на корневую таблицу).
ai_data
– непосредственно таблица, которая меняется в зависимости от того, с игроком бот имеет дело или нет(переменная просто ссылается на нужную таблицу).
На самом деле в игре есть только одна таблица, связанная с ИИ, и условно выглядит она так:
local ai = {
standard = {
peace = {
conquer = 0.0001,
war = 0.0002,
war_neighbour = 0.004,
pact = 0.005,
agree_pact = 0.018,
alliance = 0.001,
alliance_small = 0.002,
agree_alliance = 0.002,
kick = 0.0005,
envy = 0.02
},
war = {
agree_peace = 0.005,
},
player = {
peace = {
war = 0.0002,
war_neighbour = 0.002,
pact = 0.008,
agree_pact = 0.08,
alliance = 0.001,
alliance_small = 0.002,
agree_alliance = 0.002,
kick = 0.0005,
support = 0.005,
voluntary_support = 0.002,
envy = 0.01
},
war = {
agree_peace = 0.02,
}
},
bonuses = {
population_increase = 1.4,
money_increase = 1.5,
upgrade_province = 0.005
}
},
}
standard
– уровень сложности. Уровней сложности несколько, и значения в разных таблицах различаются.
peace
, war
– состояния страны. Таблица peace
используется для любых действия, таблица war
– только для действий с противником.
Третья таблица player
содержит таблицы peace
и war
с измененными значениями. Как вы уже знаете, в игре ИИ может по-разному относиться к игроку и боту.
Значения в таблицах, я думаю, понятны по названию. Но в любом случае некоторые из них мы рассмотрим.
А пока нам нужно разобраться, как же устроен ИИ в игре. Здесь все просто, в зависимости от состояния бот может выбрать любое значение из списка. Числа, которые выше, — это вероятность наступления каждого события. Рассмотрим на примере:
pact = 0.005
– означает, что с шансом 0.005 страна предложит другой стране заключить пакт о ненападении. Да, все просто. Вы можете сказать: «Как тогда можно играть, зная, что ИИ все делает наугад?». На самом деле не совсем так и это мы разберем чуть позже.
А пока посмотрим на следующую функцию:
function get_answer(option,state)
local sum_option = 0
for k, v in pairs(state) do
sum_option = sum_option + v
if option < sum_option then
return k
end
end
if sum_option > 1 then
print("AI Error, sum_option > 1")
end
end
option
– Случайное число от 0 до 1
state
– Состояние страны
Функция get_answer
просто возвращает случайное действие. Зачем она нужна, и почему нельзя просто проверять option
< шанс_действия, думаю, объяснять не требуется.
Эта функция проверяется во всех возможных действиях для ИИ:
if возможно_предложить_пакт and get_answer(option, ai_data.peace) == "pact" then
предлагаем_пакт
end
Все просто. На этом, казалось бы, можно и закончить, но нет.
Проясним несколько моментов:
- ИИ с разным шансов объявляет войну соседям и не соседям(
war
,war_neighbour
) - Маленькие страны чаще вступают в союзы по сравнению с крупными(
alliance
,alliance_small
) - У ИИ есть зависть(
envy
). По названию не совсем понятно, но это шанс, с которым страна объявит войну слишком сильной по меркам игры державе. - Мы забыли про таблицу бонусов(
bonuses
). В ней хранятся значения, которые никак не связаны с отношением к другим странам, например, бонус к росту населения или шанс улучшения провинции.
На этом интересное не кончилось(если Вам, конечно, было интересно). Странно, когда страна ни с того ни с сего объявляет войну, и еще более странно, когда она к этой войне не готова. Поэтому у ИИ в игре есть стратегии. Объясню, вместо внезапного нападения в большинстве случаев страна начинает стратегию.
У каждой страны в файле сохранения есть следующие поля:
strategy
– идентификатор стратегии. Бывает:retreat
– страна уменьшает свою армию, чтобы накопить денег.recruit
– страна нанимает максимальную армию.
target
– цель. Это любая страна, которой наш ИИ решил объявить войну, но начал стратегию.ultimatum
– nil, если нет ультиматума, данные об ультиматуме, если он есть.value
– количество ходов до объявления войны. Придумать название переменной получше не смог, извините.
То есть, если ИИ начинает стратегию, то он:
- Уменьшает свою армию и копит деньги.
- Нанимает максимально возможную армию.
- Объявляет войну игроку.
С определенным шансом страна может выдвинуть ультиматум, при выполнении которого война отменяется. Например:
Уменьшите вашу армию в провинции lipetsk до 5 000, иначе мы объявим Вам войну.
Из забавного: есть один интересный баг или даже недочет, когда страна требует уменьшить армию в провинции, и игрок успевает пока не истек ультиматум потерять ее. ИИ не учитывает принадлежность провинции и, если ультиматум не соблюден, объявляет войну.
Есть две переменные, ответственные за шанс поддержки игрока(передать игроку золото). Первая работает всегда, кроме состояния войны со страной, для которой это рассчитывается. И вторая, с большим значением, учитывается, когда есть общий враг.
Также были добавлены разные переменные, например, страх(то насколько сильно армия противника должна превышать собственную, чтобы ИИ начал задумываться о мире). Все это сделано ради того, чтобы действия ИИ казались более логичными и понятными. Но все равно, теперь мы знаем, что на самом деле это обычная замаскированная случайность.
Еще я заметил, что игроки действительно наделяют ИИ какими-то чертами, несвойственными ему. Например, если в определенный момент игроку объявят войну несколько стран, то он подумает в первую очередь, что это сговор против него, а не всего лишь совпадение. И если игроку пришлют предложение о заключении пакта о ненападении, тот подумает, что сосед боится за свои земли. Но, к сожалению, это не так. Реальность полна разочарований.