新版太阳神三国杀武将技能AI设计概要.docx
《新版太阳神三国杀武将技能AI设计概要.docx》由会员分享,可在线阅读,更多相关《新版太阳神三国杀武将技能AI设计概要.docx(24页珍藏版)》请在冰豆网上搜索。
新版太阳神三国杀武将技能AI设计概要
新版太阳神三国杀武将技能AI设计概要
第一章:
AI概述
第二章:
触发技与响应询问
一、响应技能发动询问
二、响应卡牌使用询问
三、响应卡牌打出询问
四、响应选择询问
五、响应角色选择询问
六、响应卡牌选择询问
七、响应五谷丰登选牌询问
八、响应卡牌展示询问
九、响应花色询问
十、响应遗计询问
十一、响应拼点询问
十二、响应弃牌询问
第三章:
视为技与技能发动
一、一般视为技与卡牌使用
二、一般视为技与卡牌响应
三、判定锁定视为技
四、使用技能卡
第四章:
AI特征信息
一、使用价值与使用优先级
二、相合花色与相合卡牌
三、武将嘲讽值
四、仇恨值与角色关系
第五节:
常用AI函数
第一章AI概述
正如《新版太阳神三国杀武将扩展学习手册》所描述的那样,太阳神三国杀的AI系统还是有一定章法可循的。
武将技能AI作为AI系统的重要组成部分,自然也不例外。
手册第八章里简单介绍了AI的概念和作用,给我们留下了一个初步的印象;这里将更进一步,感受一下更具体的武将技能AI设计工作。
说起来,武将技能AI还是与具体的武将技能有着密不可分的联系。
技能的种类、传递数据的方式,许多因素都影响着武将技能AI的设计。
触发技有触发技的AI策略,视为技也有自己的一套AI实现方法。
具体到技能上,更是花样百出,各有不同。
不过,掌握了武将技能AI的工作方法,再进行具体的设计,就容易很多了。
一般而言,距离修改技、手牌上限技之类的技能,在游戏里以锁定技的样貌出现,不需要玩家干预。
不论操作者是人类玩家还是电脑玩家,都是同样的效果,所以这些技能理论上是不需要AI支持的。
对于触发技来说,电脑玩家只需要知道在需要作出选择的时候,如何找出正确的答案即可。
为此,AI系统提供了一系列的决策表和决策函数。
每当遇到触发技询问时,首先查阅决策表中是否有对应的决策函数。
若找到这样的函数,就根据函数的结果作出选择;若没有找到,就交给AI系统统一处理,得出一个一般情形下的选择结果。
所以,触发技AI的设计,其实就是针对技能中出现的各个询问场景,编写对应决策函数的过程。
每个询问场景都会有自己特定的决策函数格式,按照这些格式写决策函数,就会很容易设计出触发技的AI了。
而对于视为技来说,情况又有所不同。
视为技没有特定的触发时机,需要玩家主动选择发动。
这需要考虑两个方面:
什么时候发动,以及要以怎样的方式发动。
在这个过程中,AI系统会依次检查玩家拥有的卡牌,判断哪些卡牌可用,并参照可用卡牌的优先级、使用价值等特征信息,选出当前最应使用的卡牌(包括技能卡和实际的卡牌),按照对应的卡牌使用方式使用它。
每当使用完一张卡牌,再重复这个过程,直到不再有卡牌可用。
这样,视为技的AI设计,一方面要提供判断卡牌是否可用的检测函数,一方面要提供包含了卡牌使用方式的执行函数。
此外,还要提供一些相关的特征信息,以决定在所有卡牌使用过程中的出场顺序。
除了武将技能AI以外,AI系统还包括了身份识别等更为丰富的内容,这里就不再关注了。
关于AI系统的更为详尽的介绍,可以参考太阳神三国杀extension-doc文件夹中的11-Fundamentals.lua~18-Beta.lua等文档。
第二章触发技与响应询问
一、响应技能发动询问
技能代码中出现askForSkillInvoke()函数的地方就是询问是否发动技能的场景了。
它的函数原型是:
Room:
askForSkillInvoke(player,skill_name,data)
其中:
player:
ServerPlayer类型,表示被询问发动技能的当前角色。
skill_name:
string类型,表示技能的名字。
data:
QVariant类型,表示传递给AI系统的参考数据。
另一个函数原型是:
ServerPlayer:
askForSkillInvoke(skill_name,data)
参数skill_name和data的涵义与第一个原型中的同名参数是相同的。
对于这个场景,AI系统的处理函数是:
SmartAI:
askForSkillInvoke(skill_name,data)
这个函数首先将通过表sgs.ai_skill_invoke查找对应的决策函数,而这个决策函数正是我们要具体设计的内容。
响应技能发动询问的决策函数原型是:
function(self,data)
其中:
self:
表示表SmartAI,由AI系统提供。
data:
表示参考数据,其实就是技能代码中传递过来的data参数了。
它的执行结果是bool类型的。
如果为true,表示发动技能;为false的话,则不发动这个技能。
这个表self(或者说表SmartAI)提供了许多有用的信息,比如:
self.player就是Room:
askForSkillInvoke(player,skill_name,data)中的第一个参数player,ServerPlayer类型,表示当前响应询问的角色。
self.room表示当前所在的游戏房间,Room类型。
self.friends表示当前角色的所有友方角色,也包括自己在内,table类型。
这里的友方不一定意味着身份的相同,主公与忠臣、忠臣与内奸、主公与内奸、内奸与反贼之间在一定时段內都可能会是友方关系。
self.friends_noself表示当前角色的除自身以外的所有友方角色,table类型。
self.enemies表示当前角色的所有敌方角色,table类型。
同self.friends的情形一样,身份的差异也不一定意味着阵营的对立。
将我们的决策函数写入表sgs.ai_skill_invoke的方法是:
sgs.ai_skill_invoke[skill_name]=function(self,data)
这就将技能名skill_name与特定的决策函数对应起来了。
如果技能是否发动与决策无关,那么决策函数可以简化成一个对应的bool值,比如:
sgs.ai_skill_invoke[skill_name]=true
就表示技能skill_name始终发动。
如果SmartAI:
askForSkillInvoke(skill_name,data)函数没有找到所需要的决策函数,那么它将按照默认的方式处理这个场景。
即,如果技能skill_name的触发频率是sgs.Skill_NotFrequent,那么产生一个false结果,不发动这个技能;如果触发频率是sgs.Skill_Frequent,那么产生一个true结果,发动技能。
对于其它场景,AI系统也是大体依照这样的流程进行处理的,后文不再赘述。
二、响应卡牌使用询问
技能代码中出现askForUseCard()函数的地方就是询问使用卡牌的场景了。
函数原型是:
Room:
askForUseCard(
player,pattern,prompt,notice_index=-1,method=sgs.Card_MethodUse
)
其中:
player:
ServerPlayer类型,表示被询问使用卡牌的当前角色。
pattern:
string类型,表示卡牌应符合的条件。
prompt:
string类型,表示询问时出现的提示信息。
notice_index:
number类型,表示提示信息的编号,默认为-1。
method:
sgs.Card_HandlingMethod类型,表示卡牌的处理方式,默认是sgs.Card_MethodUse。
对于这个场景,AI的处理函数是:
SmartAI:
askForUseCard(pattern,prompt,method)
三个参数pattern、prompt、method和技能代码中的同名参数是相同的。
这个函数通过表sgs.ai_skill_use来查找决策函数。
响应卡牌使用询问的决策函数原型是:
function(self,prompt,method)
它的执行结果是一个string,表示卡牌的使用方式。
具体的格式是:
#N:
C:
->U
#N[S:
P]:
C:
->U
@N=C->U
@N[S:
P]=C->U
其中:
N表示卡牌的对象名(objectName())。
S表示卡牌的花色字符串(getSuitString())。
P表示卡牌的点数(getNumber())。
C表示卡牌的子卡构成,是由一系列通过“+”连接的卡牌编号(getId())组成的字符串。
”.”表示没有子卡。
U表示卡牌的使用对象构成,是由一系列通过“+”连接的角色的对象名(objectName())组成的字符串。
”.”表示不指定使用对象。
以“#”开始的string结果表示Lua卡牌的使用方式,而以“@”开始的string结果表示C++卡牌的使用方式。
因此我们在设计武将技能AI时,写出的卡牌的使用方式,都是以“#”开始的。
将决策函数写入表sgs.ai_skill_use的方法是:
sgs.ai_skill_use[pattern]=function(self,prompt,method)
如果SmartAI:
askForUseCard(pattern,prompt,method)函数没用找到这个决策函数的话,默认产生的结果是”.”,表示不使用任何卡牌。
三、响应卡牌打出询问
技能代码中出现askForCard()函数的地方就是询问打出卡牌的场景了。
这个函数的原型是:
Room:
askForCard(player,pattern,prompt,data,skill_name)
以及
Room:
askForCard(
player,pattern,prompt,data,method=sgs.Card_MethodDiscard,
to=NULL,isRetrial=false,skill_name
)
其中:
player:
ServerPlayer类型,表示被询问打出卡牌的当前角色。
pattern:
string类型,表示卡牌应符合的条件。
prompt:
string类型,表示询问时出现的提示信息。
data:
QVariant类型,表示参考数据,还是从技能代码里传递过来的。
skill_name:
string类型,表示技能的名字。
method:
sgs.Card_andlingMethod类型,表示对卡牌的处理方式,默认是sgs.Card_MethodDiscard。
to:
ServerPlayer类型,默认为nil。
isRetrial:
bool类型,表示是否用于改判,默认为false。
对于这个场景,AI的处理函数是:
SmartAI:
askForCard(pattern,prompt,data)
这个函数通过表sgs.ai_skill_cardask来查找决策函数。
响应卡牌打出询问的决策函数原型是:
function(self,data,pattern,target,target2)
和响应卡牌使用询问的决策函数一样,它的结果也是string类型的,表示卡牌的使用方式。
将决策函数写入表sgs.ai_skill_cardask的方法是:
sgs.ai_skill_cardask[prompt]=function(self,data,pattern,target,target2)
如果SmartAI:
askForCard(pattern,prompt,data)函数没有找到这个决策函数,AI系统会尝试着按照pattern和prompt决定打出哪些卡牌。
如果仍然找不到符合条件的卡牌,那么将产生”.”的结果,表示不打出任何卡牌。
四、响应选择询问
技能代码中出现askForChoice()函数的地方就是询问选择的场景了。
这个函数的原型是:
Room:
askForChoice(player,skill_name,choices,data)
其中:
player:
ServerPlayer类型,表示被询问选择的当前角色。
skill_name:
string类型,表示技能的名字。
choices:
string类型,表示各个选项,是由一系列通过”+”连接的选项字符串组成的。
data:
QVariant类型,表示要向AI传递的参考数据。
对于这个场景,AI系统的处理函数是:
SmartAI:
askForChoice(skill_name,choices,data)
这个函数通过表sgs.ai_skill_choice来查找决策函数。
响应选择询问的决策函数原型是:
function(self,choices,data)
它将产生一个string类型的结果,表示选出的那个选项。
将决策函数写入表sgs.ai_skill_choice的方法是:
sgs.ai_skill_choice[skill_name]=function(self,choices,data)
如果选择的结果与决策无关,那么决策函数可以直接简化成这个结果,比如:
sgs.ai_skill_choice[skill_name]=“choiceA”
就表示技能skill_name询问选择时,始终选择choiceA。
如果SmartAI:
askForChoice(skill_name,choices,data)函数仍然没有找到这个决策函数,那么AI系统首先将检查这个技能是否有默认的选择,如果有,就直接用默认的选择作为结果。
否则,AI系统将从choices里随机选出一个选项,作为最终的结果。
五、响应角色选择询问
技能代码中出现askForPlayerChosen()函数的地方就是询问选择一名角色的场景了。
这个函数的原型是:
Room:
askForPlayerChosen(player,targets,reason)
其中:
player:
ServerPlayer类型,表示被询问选择角色的当前角色。
targets:
QList类型,是一个列表,表示各个备选的角色。
reason:
string类型,表示询问角色选择的原因。
对于这个场景,AI系统的处理函数是:
SmartAI:
askForPlayerChosen(targets,reason)
这个函数通过表sgs.ai_skill_playerchosen来查找决策函数。
响应角色选择询问的决策函数原型是:
function(self,targets)
它将产生一个ServerPlayer类型的结果,表示被选出的角色。
将决策函数写入表sgs.ai_skill_playerchosen的方法是:
sgs.ai_skill_playerchosen[reason]=function(self,targets)
如果SmartAI:
askForPlayerChosen(targets,reason)找不到这样的决策函数,那么AI系统将从这些备选角色中随机选择一名,作为最后的结果。
六、响应卡牌选择询问
技能代码中出现askForCardChosen()函数的地方就是询问选择一张卡牌的场景了。
这个函数的原型是:
Room:
askForCardChosen(player,who,flags,reason)
其中:
player:
ServerPlayer类型,表示被询问选择卡牌的当前角色。
who:
ServerPlayer类型,表示待选卡牌所属的目标角色。
flags:
string类型,表示待选卡牌的位置标志。
由”h”(表示手牌区)、”e”(表示装备区)、”j”(表示判定区)组合而成。
reason:
string类型,表示询问卡牌选择的原因。
对于这个场景,AI系统的处理函数是:
SmartAI:
askForCardChosen(who,flags,reason)
这个函数通过表sgs.ai_skill_cardchosen来查找决策函数。
响应卡牌选择询问的决策函数原型是:
function(self,who,flags)
它将产生一个number类型的结果,表示选出的卡牌的编号。
将决策函数写入表sgs.ai_skill_cardchosen的方法是:
sgs.ai_skill_cardchosen[reason]=function(self,who,flags)
如果选择的结果与决策过程无关,那么可以决策函数可以简化为具体的卡牌编号。
比如:
sgs.ai_skill_cardchosen[reason]=23
就表示在响应原因为reason的卡牌选择询问时,始终选择编号为23的卡牌(杀[♥10])。
如果SmartAI:
askForCardChosen(who,flags,reason)找不到这个决策函数,那么AI系统将根据who是否为友方、flags包含的区域以及具体的reason作出一般情形下的选择。
这个选择的过程较为复杂,具体可参考smart-ai.lua中该函数的具体代码。
七、响应五谷丰登选牌询问
技能代码中出现askForAG()函数的地方就是询问从五谷丰登界面选择一张卡牌的场景了。
这个函数的原型是:
Room:
askForAG(player,card_ids,refusable,reason)
其中:
player:
ServerPlayer类型,表示被询问五谷丰登选牌的当前角色。
card_ids:
QList类型,表示五谷丰登界面中所有备选卡牌的编号列表。
refusable:
bool类型,表示是否可以拒绝响应此询问。
reason:
string类型,表示询问选择的原因。
对于这个场景,AI系统的处理函数是:
SmartAI:
askForAG(card_ids,refusable,reason)
这个函数通过表sgs.ai_skill_askforag来查找决策函数。
响应五谷丰登选牌询问的决策函数原型是:
function(self,card_ids)
它将产生一个number类型的结果,表示选出的卡牌的编号。
将决策函数写入表sgs.ai_skill_askforag的方法是:
sgs.ai_skill_askforag[reason]=function(self,card_ids)
如果SmartAI:
askForAG(card_ids,refusable,reason)找不到这样的决策函数,那么AI系统将根据refusable、reason以及self.player自身的技能和对卡牌的需求程度作出一般情形下的选择。
这个选择的过程同样较为复杂,具体可以参考smart-ai.lua中该函数的具体代码。
八、响应卡牌展示询问
技能代码中出现askForCardShow()函数的地方就是询问展示一张卡牌的场景了。
这个函数的原型是:
Room:
askForCardShow(player,requestor,reason)
其中:
player:
ServerPlayer类型,表示被询问展示卡牌的当前角色。
requestor:
ServerPlayer类型,表示发起询问的源角色。
reason:
string类型,表示询问卡牌展示的原因。
对于这个场景,AI系统的处理函数是:
SmartAI:
askForCardShow(requestor,reason)
这个函数通过表sgs.ai_cardshow来查找决策函数。
响应卡牌展示询问的决策函数原型是:
function(self,requestor)
这个函数将产生一个Card类型的结果,表示将展示的卡牌。
将决策函数写入表sgs.ai_cardshow的方法是:
sgs.ai_cardshow[reason]=function(self,requestor)
如果SmartAI:
askForCardShow(requestor,reason)没用找到这样的决策函数,那么AI系统将从当前角色的手牌中随机选择一张作为结果以进行展示。
九、响应花色询问
技能代码中出现askForSuit()函数的地方就是询问选择一种花色的场景了。
这个函数的原型是:
Room:
askForSuit(player,reason)
其中:
player:
ServerPlayer类型,表示被询问选择花色的当前角色。
reason:
string类型,表示询问花色的原因。
如果reason缺失的话,AI系统会将其补全为”fanjian”,并依照反间选择花色的情形处理。
对于这个场景,AI系统的处理函数是:
SmartAI:
askForSuit(reason)
这个函数通过表sgs.ai_skill_suit来查找决策函数。
响应卡牌展示询问的决策函数原型是:
function(self)
这个函数将产生一个number类型的结果,表示选出的花色编号。
其中,0、1、2、3分别表示黑桃、红心、草花、方块花色。
将决策函数写入表sgs.ai_skill_suit的方法是:
sgs.ai_skill_suit[reason]=function(self)
如果SmartAI:
askForSuit(reason)没用找到这样的决策函数,那么AI系统将从0~3中随机选出一个数字作为最后选择的结果。
十、响应遗计询问
技能代码中出现askForYiji()函数的地方就是询问遗计分牌的场景了。
这个函数的原型是:
Room:
askForYiji(guojia,cards,is_preview=true,visible=false)
其中:
guojia:
ServerPlayer类型,表示被询问遗计分牌的当前角色。
cards:
QList类型,表示待分配的卡牌的编号列表。
is_preview:
bool类型,默认为true。
visible:
bool类型,表示卡牌在分配过程中是否对所有角色可见,默认为false。
原本这个函数只是技能“遗计”专用的,后来随着“礼让”等技能的出现,才开放出来,有了进行额外的AI设计的可能。
对于这个场景,AI系统的处理函数是:
SmartAI:
askForYiji(card_ids,reason)
这个函数是通过表sgs.ai_skill_askforyiji来查找决策函数的。
响应遗计询问的决策函数原型是:
function(self,card_ids)
这个函数将产生两个结果,依次是ServerPlayer类型(表示分牌的目标角色)和number类型(表示分给目标角色的卡牌的编号)的。
将决策函数写入表sgs.ai_skill_askforyiji的方法是:
sgs.ai_skill_askforyiji[reason]=function(self,card_ids)
如果SmartAI:
askForYiji(card_ids,reason)没有找到这个决策函数,那么AI系统将根据self.player自身存在的标志,采用“礼让”或“遗计”的策略产生分牌的结果。
这部分内容较为复杂,具体可以参考smart-ai.lua中此函数的相关代码。
十一、响应拼点询问
技能代码中出现askForPindian()函数的地方就是询问打出一张卡牌拼点的场景了。
这个函数的原型是:
Room:
askForPindian(player,from,to,reason)
其中:
player:
ServerPlayer