[Из песочницы] Как я писал ИИ для пошаговой стратегии

Всем привет. Думаю, что из заголовка ясно, что речь пойдет о создании искусственного интеллекта(далее просто ИИ), о том какие решения были приняты и что в итоге получилось. Но вначале необходимо ввести Вас в курс дела.

Игра написана на языке программирования Lua, поэтому и примеры кода я буду приводить на этом языке.

Опишу некоторые детали игры, важные для ИИ:

  1. Игра – пошаговая стратегия. Вначале ходит игрок, потом ИИ делает свои действия за каждую страну. ИИ работает только при нажатии Следующий ход и понятия не имеет, что происходит в другое время.
  2. В игре есть карта, на которой можно рекрутировать/перемещать/распускать войска. ИИ должен анализировать ее и принимать необходимые решения.
  3. В игре можно заключать мир/объявлять войну/подписывать пакт о ненападении/заключать и расторгать союзы. ИИ должен уметь справляться и с этим.
  4. Технологии и политические институты доступны только игроку. У ИИ бонусы не меняются с начала игры, в отличие от игрока.


Функция, которая отвечает за начало следующего хода выглядит так:

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

Пока мы не будем глубоко разбираться, как ИИ принимает решения в политике, а заметим следующее:

  1. Cуществует проверка, чтобы ИИ случайно не пошел за игрока или за непонятное Undeveloped_land. Это просто земля без страны(На самом деле у провинции всегда должна быть страна, владеющая ей, поэтому Undeveloped_land – это обозначение в первую очередь для игры).
  2. ИИ выбирает разные таблицы для взаимодействия со страной в зависимости от того, игрок ей управляет или нет.
  3. ИИ вначале перемещает армию, и лишь потом нанимает.

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

Все просто. На этом, казалось бы, можно и закончить, но нет.

Проясним несколько моментов:

  1. ИИ с разным шансов объявляет войну соседям и не соседям(war, war_neighbour)
  2. Маленькие страны чаще вступают в союзы по сравнению с крупными(alliance, alliance_small)
  3. У ИИ есть зависть(envy). По названию не совсем понятно, но это шанс, с которым страна объявит войну слишком сильной по меркам игры державе.
  4. Мы забыли про таблицу бонусов(bonuses). В ней хранятся значения, которые никак не связаны с отношением к другим странам, например, бонус к росту населения или шанс улучшения провинции.

На этом интересное не кончилось(если Вам, конечно, было интересно). Странно, когда страна ни с того ни с сего объявляет войну, и еще более странно, когда она к этой войне не готова. Поэтому у ИИ в игре есть стратегии. Объясню, вместо внезапного нападения в большинстве случаев страна начинает стратегию.

У каждой страны в файле сохранения есть следующие поля:

  • strategy – идентификатор стратегии. Бывает:
    1. retreat – страна уменьшает свою армию, чтобы накопить денег.
    2. recruit – страна нанимает максимальную армию.
  • target – цель. Это любая страна, которой наш ИИ решил объявить войну, но начал стратегию.
  • ultimatum – nil, если нет ультиматума, данные об ультиматуме, если он есть.
  • value – количество ходов до объявления войны. Придумать название переменной получше не смог, извините.

То есть, если ИИ начинает стратегию, то он:

  1. Уменьшает свою армию и копит деньги.
  2. Нанимает максимально возможную армию.
  3. Объявляет войну игроку.

[Из песочницы] Как я писал ИИ для пошаговой стратегии

С определенным шансом страна может выдвинуть ультиматум, при выполнении которого война отменяется. Например:
Уменьшите вашу армию в провинции lipetsk до 5 000, иначе мы объявим Вам войну.

Из забавного: есть один интересный баг или даже недочет, когда страна требует уменьшить армию в провинции, и игрок успевает пока не истек ультиматум потерять ее. ИИ не учитывает принадлежность провинции и, если ультиматум не соблюден, объявляет войну.

Есть две переменные, ответственные за шанс поддержки игрока(передать игроку золото). Первая работает всегда, кроме состояния войны со страной, для которой это рассчитывается. И вторая, с большим значением, учитывается, когда есть общий враг.

Также были добавлены разные переменные, например, страх(то насколько сильно армия противника должна превышать собственную, чтобы ИИ начал задумываться о мире). Все это сделано ради того, чтобы действия ИИ казались более логичными и понятными. Но все равно, теперь мы знаем, что на самом деле это обычная замаскированная случайность.

Еще я заметил, что игроки действительно наделяют ИИ какими-то чертами, несвойственными ему. Например, если в определенный момент игроку объявят войну несколько стран, то он подумает в первую очередь, что это сговор против него, а не всего лишь совпадение. И если игроку пришлют предложение о заключении пакта о ненападении, тот подумает, что сосед боится за свои земли. Но, к сожалению, это не так. Реальность полна разочарований.

 

Источник

gamedev, lua, разработка игр

Читайте также