Smart ai (未完成) (#276)

改smart-ai... 还没改完呢,先合了
暂时不在新版本上线,还有的改的,到时候写个注释说明一下
This commit is contained in:
notify 2023-10-07 23:05:27 +08:00 committed by GitHub
parent 183dae9ae1
commit 6376b21fbb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 151 additions and 131 deletions

View File

@ -108,9 +108,10 @@ local function useVSSkill(self, skill, pattern, cancelable, extra_data)
return nil return nil
end end
---@type table<string, fun(self: RandomAI, jsonData: string): string>
local random_cb = {} local random_cb = {}
random_cb.AskForUseActiveSkill = function(self, jsonData) random_cb["AskForUseActiveSkill"] = function(self, jsonData)
local data = json.decode(jsonData) local data = json.decode(jsonData)
local skill = Fk.skills[data[1]] local skill = Fk.skills[data[1]]
local cancelable = data[3] local cancelable = data[3]
@ -122,11 +123,11 @@ random_cb.AskForUseActiveSkill = function(self, jsonData)
return useActiveSkill(self, skill) return useActiveSkill(self, skill)
end end
random_cb.AskForSkillInvoke = function(self, jsonData) random_cb["AskForSkillInvoke"] = function(self, jsonData)
return table.random{"1", ""} return table.random{"1", ""}
end end
random_cb.AskForUseCard = function(self, jsonData) random_cb["AskForUseCard"] = function(self, jsonData)
local player = self.player local player = self.player
local data = json.decode(jsonData) local data = json.decode(jsonData)
local card_name = data[1] local card_name = data[1]
@ -169,8 +170,7 @@ random_cb.AskForUseCard = function(self, jsonData)
return "" return ""
end end
---@param self RandomAI random_cb["AskForResponseCard"] = function(self, jsonData)
random_cb.AskForResponseCard = function(self, jsonData)
local data = json.decode(jsonData) local data = json.decode(jsonData)
local pattern = data[2] local pattern = data[2]
local cancelable = true local cancelable = true
@ -186,8 +186,7 @@ random_cb.AskForResponseCard = function(self, jsonData)
return "" return ""
end end
---@param self RandomAI random_cb["PlayCard"] = function(self, jsonData)
random_cb.PlayCard = function(self, jsonData)
local cards = table.map(self.player:getCardIds(Player.Hand), local cards = table.map(self.player:getCardIds(Player.Hand),
function(id) return Fk:getCardById(id) end) function(id) return Fk:getCardById(id) end)
local actives = table.filter(self.player:getAllSkills(), function(s) local actives = table.filter(self.player:getAllSkills(), function(s)

View File

@ -1,65 +1,86 @@
-- SPDX-License-Identifier: GPL-3.0-or-later -- SPDX-License-Identifier: GPL-3.0-or-later
--[[
SmartAI: AI架构的AI体系
AI常用的种种表以及实用函数等AI逻辑的接口
AI的核心在于编程实现对各种交互的回应(room:askForXXX)
smart_cb表以实现合理的答复
便AIAI判断时常用的函数
-- TODO: 优化底层逻辑防止AI每次操作之前都要json.decode一下。
-- TODO: 更加详细的文档
--]]
---@class SmartAI: AI ---@class SmartAI: AI
local SmartAI = AI:subclass("SmartAI") local SmartAI = AI:subclass("SmartAI")
---@type table<string, fun(self: SmartAI,jsonData: any)> --[[
local smart_cb = {} * *
--]]
---[skill_name] = function(self, prompt, cancelable, data) --- 用来应对Room:askForUseActiveSkill的表。
---@type table<string, fun(self: SmartAI, prompt: string, cancelable: bool, data: any)> ---@type table<string, fun(self: SmartAI, prompt: string, cancelable: bool, data: any)>
fk.ai_use_skill = {} fk.ai_use_skill = {}
---[skillName] = function(self, targets, min_num, num, cancelable)
---@type table<string, fun(self: SmartAI, targets: integer[], min_num: number, num: number, cancelable: bool)> --- TOdo? Room:askForGeneral暂缺
fk.ai_choose_players = {}
---[skillName] = function(self, min_num, num, include_equip, cancelable, pattern, prompt) --- 用来应对Room:askForSkillInvoke的表。
---@type table<string, fun(self: SmartAI, min_num: number, num: number, include_equip: bool, cancelable: bool, pattern: string, prompt: string)>
fk.ai_discard = {}
---[skill_name] = function(self, extra_data, prompt)
---@type table<string, fun(self: SmartAI, extra_data: any, prompt: string)> ---@type table<string, fun(self: SmartAI, extra_data: any, prompt: string)>
fk.ai_skill_invoke = {} fk.ai_skill_invoke = {}
---[prompt:split(":")[1]] = function(self, id_list, cancelable, prompt)
--- 用来应对Room:askForAG的表。表的键是prompt的第一项。
---@type table<string, fun(self: SmartAI, id_list: integer[], cancelable: bool, prompt: string)> ---@type table<string, fun(self: SmartAI, id_list: integer[], cancelable: bool, prompt: string)>
fk.ai_ask_forag = {} fk.ai_ask_for_ag = {}
---[card.name] = function(self, card)
--- --- 用来应对出牌阶段空闲时间点如何出牌/使用技能的表。
---[skill.name] = function(self, skill)
---@type table<string, fun(self: SmartAI, card: Card|ActiveSkill|ViewAsSkill)> ---@type table<string, fun(self: SmartAI, card: Card|ActiveSkill|ViewAsSkill)>
fk.ai_use_play = {} fk.ai_use_play = {}
---[prompt:split(":")[1]] = function(self, pattern, prompt, cancelable, extra_data)
--- --- 用来应对Room:askForUseCard的表。表的键是prompt的第一项或者牌名优先prompt。
---[card_name] = function(self, pattern, prompt, cancelable, extra_data)
---@type table<string, fun(self: SmartAI, pattern: string, prompt: string, cancelable: bool, extra_data: any)> ---@type table<string, fun(self: SmartAI, pattern: string, prompt: string, cancelable: bool, extra_data: any)>
fk.ai_ask_usecard = {} fk.ai_ask_usecard = {}
---[effect.card.name] = function(self, effect.card, room:getPlayerById(effect.to), room:getPlayerById(effect.from), positive) ---[effect.card.name] = function(self, effect.card, room:getPlayerById(effect.to), room:getPlayerById(effect.from), positive)
---@type table<string, fun(self: SmartAI, card: Card, to: ServerPlayer, from: ServerPlayer, positive: bool)> ---@type table<string, fun(self: SmartAI, card: Card, to: ServerPlayer, from: ServerPlayer, positive: bool)>
fk.ai_nullification = {} fk.ai_nullification = {}
---[card.name] = {intention = 0, value = 0, priority = 0} ---[card.name] = {intention = 0, value = 0, priority = 0}
--- ---
---[skill.name] = {intention = 0, value = 0, priority = 0} ---[skill.name] = {intention = 0, value = 0, priority = 0}
---@type table<string, {intention: number, value: number, priority: number}> ---@type table<string, {intention: number, value: number, priority: number}>
fk.ai_card = {} fk.ai_card = {}
---[card.id] = 0 ---[card.id] = 0
--- ---
---[skill.name] = 0 ---[skill.name] = 0
---@type table<string|number, number> ---@type table<string|number, number>
fk.cardValue = {} fk.cardValue = {}
---[prompt:split(":")[1]] = function(self, pattern, prompt, cancelable, extra_data) ---[prompt:split(":")[1]] = function(self, pattern, prompt, cancelable, extra_data)
--- ---
---[card_name] = function(self, pattern, prompt, cancelable, extra_data) ---[card_name] = function(self, pattern, prompt, cancelable, extra_data)
---@type table<string, fun(self: SmartAI, pattern: string, prompt: string, cancelable: bool, extra_data: any)> ---@type table<string, fun(self: SmartAI, pattern: string, prompt: string, cancelable: bool, extra_data: any)>
fk.ai_response_card = {} fk.ai_response_card = {}
---[reason] = function(self, to, flag) ---[reason] = function(self, to, flag)
---@type table<string, fun(self: SmartAI, to: ServerPlayer, flag: string)> ---@type table<string, fun(self: SmartAI, to: ServerPlayer, flag: string)>
fk.ai_card_chosen = {} fk.ai_card_chosen = {}
---[reason] = function(self, to, min, max, flag) ---[reason] = function(self, to, min, max, flag)
fk.ai_cards_chosen = {} fk.ai_cards_chosen = {}
---[skill_name] = function(self, choices, prompt, detailed, all_choices) ---[skill_name] = function(self, choices, prompt, detailed, all_choices)
---@type table<string, fun(self: SmartAI, choices: string[], prompt: string, detailed: bool, all_choices: string[])> ---@type table<string, fun(self: SmartAI, choices: string[], prompt: string, detailed: bool, all_choices: string[])>
fk.ai_ask_choice = {} fk.ai_ask_choice = {}
---[judge.reason] = {judge.pattern,isgood} ---[judge.reason] = {judge.pattern,isgood}
---@type table<string, {pattern: string,isgood: boolean}> ---@type table<string, {pattern: string,isgood: boolean}>
fk.ai_judge = {} fk.ai_judge = {}
---[gameMode] = function(self, to) ---[gameMode] = function(self, to)
--- ---
---根据游戏模式定义目标敌友值 ---根据游戏模式定义目标敌友值
@ -68,14 +89,23 @@ fk.ai_judge = {}
---@type table<string, fun(self: SmartAI, to: ServerPlayer)> ---@type table<string, fun(self: SmartAI, to: ServerPlayer)>
fk.ai_objective_level = {} fk.ai_objective_level = {}
--[[
* SmartAI类成员函数部分 *
--]]
--[[
* command处理函数部分 *
RandomAI一样对各种请求类型返回相应的数据
SmartAI会尽可能做出合乎逻辑的决策
--]]
---@type table<string, fun(self: SmartAI, jsonData: string): string>
local smart_cb = {}
--- 请求发动主动技 --- 请求发动主动技
--- ---
--- 总的请求技,从它分支出各种功能技 --- 总的请求技,从它分支出各种功能技
---@param self SmartAI @ai系统 smart_cb["AskForUseActiveSkill"] = function(self, jsonData)
---@param jsonData any @总数据
---@return string @json使用数据包含了子卡和目标
smart_cb.AskForUseActiveSkill = function(self, jsonData)
local data = json.decode(jsonData) local data = json.decode(jsonData)
local skill = Fk.skills[data[1]] local skill = Fk.skills[data[1]]
local prompt = data[2] local prompt = data[2]
@ -100,77 +130,8 @@ smart_cb.AskForUseActiveSkill = function(self, jsonData)
return "" return ""
end end
--- 请求选择目标
---
---由skillName进行下一级的决策只需要在下一级里给self.use_tos添加角色id为目标就行
---@param self SmartAI @ai系统
---@param prompt string @提示信息
---@param cancelable boolean @可以取消
---@param data any @数据
fk.ai_use_skill.choose_players_skill = function(self, prompt, cancelable, data)
local ask = fk.ai_choose_players[data.skillName]
if type(ask) == "function" then
ask(self, data.targets, data.min_num, data.num, cancelable)
end
if #self.use_tos > 0 then
if self.use_id then
self.use_id = json.encode {
skill = data.skillName,
subcards = self.use_id
}
else
self.use_id = json.encode {
skill = data.skillName,
subcards = {}
}
end
end
end
--- 请求弃置
---
---由skillName进行下一级的决策只需要在下一级里返回需要弃置的卡牌id表就行
---@param self SmartAI @ai系统
---@param prompt string @提示信息
---@param cancelable boolean @可以取消
---@param data any @数据
fk.ai_use_skill.discard_skill = function(self, prompt, cancelable, data)
local ask = fk.ai_discard[data.skillName]
self:assignValue()
if type(ask) == "function" then
ask = ask(self, data.min_num, data.num, data.include_equip, cancelable, data.pattern, prompt)
end
if type(ask) ~= "table" and not cancelable then
local flag = "h"
if data.include_equip then
flag = "he"
end
ask = {}
local cards = table.map(self.player:getCardIds(flag), function(id)
return Fk:getCardById(id)
end
)
self:sortValue(cards)
for _, c in ipairs(cards) do
table.insert(ask, c.id)
if #ask >= data.min_num then
break
end
end
end
if type(ask) == "table" and #ask >= data.min_num then
self.use_id = json.encode {
skill = data.skillName,
subcards = ask
}
end
end
--- 请求发动技能 --- 请求发动技能
---@param self SmartAI @ai系统 smart_cb["AskForSkillInvoke"] = function(self, jsonData)
---@param jsonData any @总数据
---@return string @输出"1"为发动
smart_cb.AskForSkillInvoke = function(self, jsonData)
local data = json.decode(jsonData) local data = json.decode(jsonData)
local prompt = data[2] local prompt = data[2]
local extra_data = data[3] local extra_data = data[3]
@ -188,16 +149,13 @@ smart_cb.AskForSkillInvoke = function(self, jsonData)
end end
--- 请求AG --- 请求AG
---@param self SmartAI @ai系统 smart_cb["AskForAG"] = function(self, jsonData)
---@param jsonData any @总数据
---@return number @ 选择的牌id
smart_cb.AskForAG = function(self, jsonData)
local data = json.decode(jsonData) local data = json.decode(jsonData)
local prompt = data[3] local prompt = data[3]
local cancelable = data[2] local cancelable = data[2]
local id_list = data[1] local id_list = data[1]
self:updatePlayers() self:updatePlayers()
local ask = fk.ai_ask_forag[prompt:split(":")[1]] local ask = fk.ai_ask_for_ag[prompt:split(":")[1]]
if type(ask) == "function" then if type(ask) == "function" then
ask = ask(self, id_list, cancelable, prompt) ask = ask(self, id_list, cancelable, prompt)
end end
@ -299,10 +257,7 @@ end
---优先由prompt进行下一级的决策需要定义self.use_id如果卡牌需要目标也需要给self.use_tos添加角色id为目标 ---优先由prompt进行下一级的决策需要定义self.use_id如果卡牌需要目标也需要给self.use_tos添加角色id为目标
--- ---
---然后若没有定义self.use_id则由card_name再进行决策 ---然后若没有定义self.use_id则由card_name再进行决策
---@param self SmartAI @ai系统 smart_cb["AskForUseCard"] = function(self, jsonData)
---@param jsonData any @总数据
---@return string @json使用数据包含了子卡和目标
smart_cb.AskForUseCard = function(self, jsonData)
local data = json.decode(jsonData) local data = json.decode(jsonData)
local pattern = data[2] local pattern = data[2]
local prompt = data[3] local prompt = data[3]
@ -612,10 +567,7 @@ end
---请求打出 ---请求打出
--- ---
---优先按照prompt提示信息进行下一级决策需要定义self.use_id然后可以根据card_name再进行决策 ---优先按照prompt提示信息进行下一级决策需要定义self.use_id然后可以根据card_name再进行决策
---@param self SmartAI @ai系统 smart_cb["AskForResponseCard"] = function(self, jsonData)
---@param jsonData any @总数据
---@return string @json打出数据
smart_cb.AskForResponseCard = function(self, jsonData)
local data = json.decode(jsonData) local data = json.decode(jsonData)
local pattern = data[2] local pattern = data[2]
local prompt = data[3] local prompt = data[3]
@ -716,10 +668,7 @@ function SmartAI:cardsView(pattern)
end end
---空闲点使用 ---空闲点使用
---@param self SmartAI @ai系统 smart_cb["PlayCard"] = function(self, jsonData)
---@param jsonData any @总数据
---@return string @json使用数据
smart_cb.PlayCard = function(self, jsonData)
local cards = table.map(self.player:getHandlyIds(true), function(id) local cards = table.map(self.player:getHandlyIds(true), function(id)
return Fk:getCardById(id) return Fk:getCardById(id)
end end
@ -749,10 +698,7 @@ end
---请求选择角色区域牌 ---请求选择角色区域牌
--- ---
---按照reason原因进行下一级决策需返回选择的牌id同时设置有兜底决策 ---按照reason原因进行下一级决策需返回选择的牌id同时设置有兜底决策
---@param self SmartAI @ai系统 smart_cb["AskForCardChosen"] = function(self, jsonData)
---@param jsonData any @总数据
---@return number @牌id
smart_cb.AskForCardChosen = function(self, jsonData)
local data = json.decode(jsonData) local data = json.decode(jsonData)
local to = self.room:getPlayerById(data[1]) local to = self.room:getPlayerById(data[1])
local chosen = fk.ai_card_chosen[data[3]] local chosen = fk.ai_card_chosen[data[3]]
@ -791,10 +737,7 @@ end
---请求选择角色区域多张牌 ---请求选择角色区域多张牌
--- ---
---按照reason原因进行下一级决策需返回选择的牌id表同时设置有兜底决策 ---按照reason原因进行下一级决策需返回选择的牌id表同时设置有兜底决策
---@param self SmartAI @ai系统 smart_cb["AskForCardsChosen"] = function(self, jsonData)
---@param jsonData any @总数据
---@return string @json选择牌表数据
smart_cb.AskForCardsChosen = function(self, jsonData)
local data = json.decode(jsonData) local data = json.decode(jsonData)
local to = self.room:getPlayerById(data[1]) local to = self.room:getPlayerById(data[1])
local min = data[2] local min = data[2]
@ -842,12 +785,9 @@ end
---请求选择选项 ---请求选择选项
--- ---
---按照skill_name进行下一级决策需返回要选择的选项兜底决策是随机选择 ---按照skill_name进行下一级决策需返回要选择的选项兜底决策是随机选择
---@param self SmartAI @ai系统 smart_cb["AskForChoice"] = function(self, jsonData)
---@param jsonData any @总数据
---@return string @选择的选项
smart_cb.AskForChoice = function(self, jsonData)
local data = json.decode(jsonData) local data = json.decode(jsonData)
local choices = data[1] local choices = data[1] ---@type string[]
local all_choices = data[2] local all_choices = data[2]
local prompt = data[4] local prompt = data[4]
local detailed = data[5] local detailed = data[5]
@ -855,7 +795,7 @@ smart_cb.AskForChoice = function(self, jsonData)
if type(chosen) == "function" then if type(chosen) == "function" then
chosen = chosen(self, choices, prompt, detailed, all_choices) chosen = chosen(self, choices, prompt, detailed, all_choices)
end end
return table.connect(choices,chosen) and chosen or table.random(choices) return table.contains(choices,chosen) and chosen or table.random(choices)
end end
fk.ai_judge.indulgence = { ".|.|heart", true } fk.ai_judge.indulgence = { ".|.|heart", true }
@ -905,7 +845,7 @@ end
---@param self SmartAI @ai系统 ---@param self SmartAI @ai系统
---@param jsonData any @总数据 ---@param jsonData any @总数据
---@return string @json放置顶和底的牌id表 ---@return string @json放置顶和底的牌id表
smart_cb.AskForGuanxing = function(self, jsonData) smart_cb["AskForGuanxing"] = function(self, jsonData)
local data = json.decode(jsonData) local data = json.decode(jsonData)
local cards = table.map(data.cards, function(id) local cards = table.map(data.cards, function(id)
return Fk:getCardById(id) return Fk:getCardById(id)

View File

@ -408,6 +408,11 @@ function GameLogic:getCurrentEvent()
return self.game_event_stack.t[self.game_event_stack.p] return self.game_event_stack.t[self.game_event_stack.p]
end end
---@param eventType integer
function GameLogic:getMostRecentEvent(eventType)
return self:getCurrentEvent():findParent(eventType, true)
end
--- 如果当前事件刚好是技能生效事件,就返回那个技能名,否则返回空串。 --- 如果当前事件刚好是技能生效事件,就返回那个技能名,否则返回空串。
function GameLogic:getCurrentSkillName() function GameLogic:getCurrentSkillName()
local skillEvent = self:getCurrentEvent() local skillEvent = self:getCurrentEvent()

View File

@ -0,0 +1,74 @@
-- aux_skill的AI文件。aux_skill的重量级程度无需多说。
-- 这个文件说是第二个smart_ai.lua也不为过。
-- discard_skill: 弃牌相关AI
-----------------------------
--- 弃牌相关判定函数的表。键为技能名,值为原型如下的函数。
---@type table<string, fun(self: SmartAI, min_num: number, num: number, include_equip: bool, cancelable: bool, pattern: string, prompt: string): any>
fk.ai_discard = {}
--- 请求弃置
---
---由skillName进行下一级的决策只需要在下一级里返回需要弃置的卡牌id表就行
fk.ai_use_skill["discard_skill"] = function(self, prompt, cancelable, data)
local ask = fk.ai_discard[data.skillName]
self:assignValue()
if type(ask) == "function" then
ask = ask(self, data.min_num, data.num, data.include_equip, cancelable, data.pattern, prompt)
end
if type(ask) ~= "table" and not cancelable then
local flag = "h"
if data.include_equip then
flag = "he"
end
ask = {}
local cards = table.map(self.player:getCardIds(flag), function(id)
return Fk:getCardById(id)
end
)
self:sortValue(cards)
for _, c in ipairs(cards) do
table.insert(ask, c.id)
if #ask >= data.min_num then
break
end
end
end
if type(ask) == "table" and #ask >= data.min_num then
self.use_id = json.encode {
skill = data.skillName,
subcards = ask
}
end
end
-- choose_players_skill: 选人相关AI
-------------------------------------
--- 选人相关判定函数的表。键为技能名,值为原型如下的函数。
---@type table<string, fun(self: SmartAI, targets: integer[], min_num: number, num: number, cancelable: bool)>
fk.ai_choose_players = {}
--- 请求选择目标
---
---由skillName进行下一级的决策只需要在下一级里给self.use_tos添加角色id为目标就行
fk.ai_use_skill["choose_players_skill"] = function(self, prompt, cancelable, data)
local ask = fk.ai_choose_players[data.skillName]
if type(ask) == "function" then
ask(self, data.targets, data.min_num, data.num, cancelable)
end
if #self.use_tos > 0 then
if self.use_id then
self.use_id = json.encode {
skill = data.skillName,
subcards = self.use_id
}
else
self.use_id = json.encode {
skill = data.skillName,
subcards = {}
}
end
end
end

View File

@ -1,3 +1,5 @@
require "packages.standard.ai.aux_skills"
fk.ai_use_play["rende"] = function(self, skill) fk.ai_use_play["rende"] = function(self, skill)
for _, p in ipairs(self.friends_noself) do for _, p in ipairs(self.friends_noself) do
if p.kingdom == "shu" and #self.player:getCardIds("h") >= self.player.hp then if p.kingdom == "shu" and #self.player:getCardIds("h") >= self.player.hp then