这是转别人的帖子。
SanIX PK1.01程序代码分析 (2005-12-22) 所有结果均基于对PK版1.01的汇编代码的跟踪分析得出,而且是在普通剧本环境下得出,因此想磨练史话一类剧本中的事件可能不遵循下述结果。 如无特别说明,公式中的除法均为整数除法,因此运算顺序绝不可轻易调换,不满足交换率和结合率。 由于跟踪时仅记录了公式中的主要变量,一些系数可能有所出入,但并不影响这个公式在函数关系上的正确性。 请保持此文档的完整性,转载请注明。 一、内政部分 1、人口变动模型 只有都市才具有人口属性,因此人口的变动仅针对都市而言。人口的变动主要包括非兵役人口增量和增加兵役两个方面。基本的变化规律及影响因素不难知道,大体上民心越高则越有利,士兵越多则兵役人口增长越缓慢。然而准确的公式我还没有见到有人给出过。 程序在计算每个城市的人口变动时,首先计算出总非兵役人口增量、总增加兵役人口及总民心等几个中间参数,然后再分配到各个都市,这几个中间参数的计算如下: 总非兵役人口增量=总非兵役人口/20 总增加兵役=max(总非兵役人口-总兵力+99999,0)/100000×总非兵役人口/100/10 总民心=所有都市民心之和 总非兵役人口=所有都市非兵役人口之和 总兵力=所有都市兵役人口之和+所有士兵+所有伤兵 假定初始时每个都市民心为100,人口为30000,并且假定10年内所有都市只搞内政,不征兵则10年内总人口及总兵役人口的变化如下: 有了以上中间参数,则每个都市的人口变动计算如下: 非兵役人口增量=max(民心×总非兵役人口增量/总民心-非兵役人口/20,30000-非兵役人口) 增加兵役=民心×总增加兵役/总民心 如果非兵役人口增量为负,则人口流失,都市非兵役人口减少,反之则增加。由于总增加兵役不可能为负,因此增加兵役不可能为负。 关于人口计算模型的几点评注: l 考察都市非兵役人口增量,如果忽略max函数的后一项,则容易得到人口流失的条件是非兵役人口/总非兵役人口>民心/总民心,即当一个城市的人口所占比例太大时,就会流失。因此,即便是民心1000,也有可能出现人口流失。假定所有都市民心一样,那么人口流动的最终结果将是所有都市非兵役人口都一样; l 考察都市的增加兵役,由于总增加兵役可能会是一个很大的数,又因为32bit数的限制,因此实际上前面两项的乘积可能已经溢出了,这意味着总增加兵役越多,都市的增加兵役未必就越多。假定所有都市民心为1000,那么不溢出的条件是总增加兵役不超过430万左右; l 总非兵役人口的增长率是固定数字,即5%。虽然程序中并未对总非兵役人口作限制,但由于每个都市非兵役人口最多为100万,因此总非兵役人口最多为5000万。还有一个隐性限制来自于32bit数; l 总增加兵役由总非兵役人口和总兵力共同决定。当总非兵役人口很大而总兵力很少时,总增加兵役可能很大,极限情况下,总增加兵役=(总非兵役人口/10000)2;而当总兵力超过总非兵役人口时,总增加兵役为0。 2、内政执行 总共有8个内政指令:巡查、商业、开垦、修筑、徽兵、训练、买进、卖出。其中前面四个指令采用统一的公式模型计算: 其中最大值为都市相关属性的上限,而总能力值为所有执行武将相关能力的综合,特定系数巡查为9/5,其他为2。由于武将人数越多,则总能力值越大,因此效果越显著,除非执行武将中废柴太多。 徽兵的计算公式较简单: 训练的计算公式较复杂,首先要对武将的统率值进行规范化处理: 稍微研究一下就可发现,此规范公式其实是武将统率的档次拉开了,小于60的部分按照系数2/3加权,60~80之间的部分按照系数1加权,而高于80的部分则按照系数2加权,因此高统率武将与低统率武将的差距经过此规范公式处理后差距就会更鲜明。后面的兵法计算公式及其他公式中也常将武将的能力值进行类似的规范处理,以划分出能力的档次。绘制成曲线更能看出档次的差距: 训练的效果大致如下(有些细节记不清了): 3、计略部分 同样,共有8种计略指令。计略的成功与否也采用确定的公式计算,不存在概率问题;外交等策略指令也采用确定公式计算,具体略去。 二、单挑部分 主要分析了单挑触发、单挑攻击力的计算代码,又以后者为重点。 相关代码地址:单挑攻击力计算:RVA = 100650。 1、单挑攻击力的计算 单挑时是一刀一刀地计算的,直到某一方的HP点数变为0分出胜负为止。以下先给出计算流程,后文再给出相关公式。 对于每一刀的伤害力,主要计算步骤如下: 1) 如果防守方看破攻击方的招数,则伤害力为0,本刀结束;否则转2); 2) 如果攻击方发动动画必杀,则伤害力取动画必杀的伤害力;否则转3); 3) 如果攻击方发动双倍必杀,则伤害力取双倍必杀的伤害力;否则转4); 4) 取普通攻击的伤害力,本刀结束。 下面给出计算公式。 变量符号: PA:攻击方武力,此武力为总武力值(意即包括宝物、爵位加成在内); HPA:攻击方剩余血点数,满血为100; CA:攻击方武将性格,莽撞~慎重分别为0~3; BA:攻击方武将固有号码; PD:防守方武力,此武力为总武力值; HPD:防守方剩余血点数,满血为100; CD:防守方武将性格,莽撞~慎重分别为0~3; BB:防守方武将固有号码。 中间参数符号: TA:攻击方的规范化武力值; TB:防守方武将的规范化武力值; B:攻击方基本伤害; 有关结果符号: P0:防守方看破攻击方招数的概率; P1:攻击方发动动画必杀的概率; P2:攻击方发动双倍必杀的概率; H0:防守方看破攻击方招数时的伤害点数(伤害点数其实就是掉血的点数,下同); H1:动画必杀的伤害点数; H2:双倍必杀的伤害点数; H:普通攻击的伤害点数。 上述公式中用到的中间参数定义: 上述公式看似庞然大物,其实仔细看一下还是不难明白的。下面是一些结论: 1)概率范围:很容易看出,一般武将看破攻击方招数的最大概率为8,而吕布和赵云在武力高出对方20点(含)以上时概率为40;动画必杀的最大概率也为8;一般武将双倍必杀的最大概率为12,而马超在武力高出对方20点(含)以上时,最大概率为24; 2)伤害力范围:很明显,一般的武将的伤害力基数上限在22左右(事实上只有“右”没有“左”),而吕布在武力高出对方20点(含)以上是,这个基数上限33左右,这意味着即便是普通攻击,只需要一点点运气,3刀即可**,这一点实战中也已可发现; 3)性格之影响:可以明显地看出,性格会影响概率,但不会影响伤害力。性格越慎重者,防守时更容易看破对方招数,防守占优;性格越莽撞者,动画必杀、双倍必杀的概率都会有所提高。这样的设定符合整个游戏对性格的定义:性格越莽撞者长于攻击,越冷静者则善于防守; 4)HP点数的影响:这一代的单挑竟然会有这种怪设定——血越少攻击力越强。可以看出,HP点数影响伤害力,但不影响概率,而且剩余的点数越少,攻击力越强。在出现动画必杀时,防守方与攻击方剩余HP点数的差对攻击力的贡献是非常大的。假定攻击方只剩一滴血,而防守方满血,此时出现动画必杀的话竟然有高达50点左右的加成,这大概是所谓“置之死地而后生”的体现吧。根据H1的计算公式,一般武将在满血时是不可能一刀就干掉对方的(最多:24*3+10=82点),但是如果剩的血比较少,则有可能一刀就干掉。当然吕布另当别论,由于可以有33以上的基数,因此吕布即使在满血时也可以一刀就干掉对手; 5)武力的影响:武力既影响概率,又影响伤害力。因此,综合起来讲,武力才是最重要的因素。而武力又按照另外一个规范化公式进行了处理,该公式将武力值划分为4个档次,档次越高每1点的实际差距就越大,具体见下面的曲线: 不过由于有基本攻击力上限和概率上限的设定,1000的武力和500的武力在单挑时实际上一样的。 6)先手优势:很明显,主动发起单挑者具有先手优势,因为总是先砍对手一刀; 7)隐藏:很明显,吕布、赵云、马超具有隐藏属性。不过这一代的隐藏设置简直是多此一举,因为所有的隐藏都要在武力比对方高出20点或更多时才有效,在武力如此悬殊的条件下,即便没有隐藏,即便换一个慎重型武将上场,我想也可轻松搞定了吧,还需要隐藏属性干什么!如此一来,吕布、赵云、马超成了“欺软”之能者。各人的隐藏属性具体如下: 吕布:当武力高于对手20点以上时,看破对方招数的概率大大增加,变为40%,而且攻击力基数上限也大大提升,略高于33点; 赵云:当武力高于对手20点以上时,看破对方招数的概率大大增加,变为40%; 马超:当武力高于对手20点以上时,出现双倍必杀的概率上限增加,即出现双倍必杀的概率变为≤24%(其他人为≤12%)。 8)应该说单挑的计算还是比较精细的,性格档次的划分、上限的设定也比较合理,唯独不好的就是隐藏属性的设置。 2、单挑阵亡 单挑阵亡是有前提条件的,大概包括:不能是君主;不能是总大将(?这一条记不清楚了);四项能力必须全低于80。阵亡最大概率为30%。 3、单挑的触发 部队中的前锋武将既可发动兵法,又可发动单挑。如果是非锋矢阵的话,触发单挑的概率是比较小的,大约为3%左右,而峰矢阵的触发概率大概会翻倍,最高可达到18%左右。一般年来讲,主动发起单挑者会有先手的优势,但游戏程序对主动发起者的武力作了限定,具体视发起者的性格而定: 莽撞:发起者武力≥接受者武力-6; 刚胆:发起者武力≥接受者武力-3; 冷静:发起者武力≥接受者武力。 慎重型武将不管武力有多高,一定不能主动发起单挑。根据以上限定及对单挑触发的其他限定可知:如果前锋中武力最高的那个最小间隔为0,那么前锋中其他武将既没有机会发起单挑,也没有机会接受单挑。 三、兵法部分 一)、兵法的发动 兵法的发动分陆地、水军、设施几种情况,其中以陆地的发动最为复杂,以下分述之。 1、在陆地上成功发动一次兵法实际上比较困难的,要经过多次筛选,诸多计算才能成功发动。陆地上成功发动一次并发的过程大体上分为2个步骤。 1)、兵法发动机会的产生 每一天,程序代码都会给满足条件的部队(所谓满足条件的部队是指:部队中至少有一名武将的最小间隔为0)1~2次的兵法发动机会,有时甚至有3次,不过总体来看满足条件的部队一旬内获得的次数大致在13~20次,决定次数的因素还未清楚,这个次数其实就是一个部队一旬内对敌方普通攻击的次数。对于每一次普通攻击,部队都有可能产生发动兵法的机会。这个机会产生的概率采用固定模型计算,而跟部队武将、阵形无关,具体概率如下: 因此部队兵法机会的把握概率在15%~30%之间。如果按照一旬15次兵法机会来计算,那么一旬内一支部队成功把握住的兵法机会次数平均2.25~4.5次左右。 2)、兵法发动机会的分配 如果部队在某一日成功把握住了一次发动机会,那么是否就一定会发动兵法呢?未必,因为还存在一个机会的分配过程,即这次机会到底应该由谁来利用。分配的算法流程大致如下: 建立可分配(最小间隔、健康状态等限制条件)的武将链表PersonList,链表中的武将按照位置排序; For each Officer in the PersonList if 分到机会 then exit else remove this officer from the PersonList end 其中是否“分到机会”的计算因武将所选的兵法不同而不同。游戏程序针对不同的兵法设计了多个分配函数,不过这些分配函数采用统一的公式计算分配到机会的概率: 按照这个计算公式,对于单人部队而言,如果阵形利于兵法的发动,而且武将位于前锋或中锋,那么一定可以成功分配到把握住的兵法机会。因此对于这种部队,能否发出兵法完全取决于部队能否成功把握住兵法机会。实际经验也表明:防守的时候,采用单人部队迎击的话,常常第一旬内就能够发出兵法。其实这正是因为在自地域作战,部队把握兵法机会的概率提高,因此一旬内把握住一次机会的概率就大大提高了,而一旦把握住,肯定不会分丢的。对于更多的情况,由于分配到兵法机会的概率<100%,因此并不是一旦把握住就一定能发动出来的。不过这个概率的最小值为25%,因此整个部队所有武将都分配不到一次把握住的机会的概率应该是比较小的,但分丢并不是不可能的。 每种兵法的分配函数中都硬编码了利于这种兵法发动的阵形代号,一般的兵法大概有那么2~3种。对于谋略系、策略系的兵法,并没有哪种特别的阵形利于发动,因此在上面的机会分配概率公式中,f(兵法,阵型)一项始终为1,因此谋略系、策略系兵法分配到兵法机会的最大概率为62.5%(单人、前锋),实战中也常常感觉到谋略系、策略系兵法不太容易发出。各种兵法的有利阵形列于下: 步兵系:0、1、15、18、19(鱼鳞、鹤翼、山越、南蛮、倭); 骑兵系:2、3、16、17(锥形、锋矢、乌丸、羌); 弓骑兵:2、5、16(锥形,箕形、乌丸); 弩兵:4、5、8(雁形、箕形、井阑)。 另外值得一提的是,偶在跟踪代码时发现这样的一个问题:对于如图所示的部队,在分配机会时似乎只判断了锋线上的武将,其他4个尚未判断就结束了,而锋线上的武将的兵法是发不出来的,因此总是把机会给分丢掉。实际试了一下,好像的确发不出兵法来,不过相信没有人会如此组队吧。 图1 不会发兵法的组队 关于兵法发动的总结: 1)、部队的状态、部队所在地域影响部队对兵法机会的产生概率; 2)、武将的位置影响分配到机会的概率,前锋/中锋=1.25倍,中锋/后卫=1.33倍; 3)、阵形对分配到机会的概率的影响很显著,有利/不利=2倍; 4)、人数也会影响分配概率。人数越多,每个人分配到的概率都会降低,不过总的分丢概率却未必会低; 5)、如果兵力允许的话,尽可能拆分组队。举个例子:3个武将30000*1组队与1000*3组队,假设全选有利阵形,全在前锋,而且假设一个部队一旬内一定能够把握住3次机会,则发动3次兵法的概率:3人队:24.4%,3*1人队:100%。 6)、由于所有的部队产生兵法机会的概率都是一样的,因此真正影响兵法发动的因素在于分配时的概率。 2、水军兵法的发动 水军兵法的发动也存在两个过程:即机会的产生与机会的分配。不过水军的情况比较简单,一旦把产生机会,程序代码会随机选择一个武将来发动兵法,而且分配时不存在概率问题,100%发动。因此,虽然水军产生兵法机会的概率低于陆地部队,但是由于不存在机会分丢的问题,因此发动次数并不一定低于陆地。此外,在水上时仍可发动谋略、策略兵法,因此总体上应该不存在谁发动次数更多的问题。不过相比而言,陆地兵法系统的设计更为精细。 3、设施兵法的发动 设施兵法由总大将发动,同样不存在分配的问题,只存在机会的产生问题。 二)、兵法威力及其他 1、兵法的伤害力 变量: P:发动者武力 E:发动者熟练度 X:电脑强度 中间参数: B:普通攻击伤害 T:兵法加成伤害 W:兵法威力 结果: HA:发动方形成的总伤害;HD:防守方形成的伤害 HAX:调整强度后的发动方伤害;HDX:调整强度后防守方的伤害 ,其中 程序中的伤害其实是同时计算的,因此在发动方形成伤害的同时,防守方也会造成伤害。正是由于这样,才会在游戏中看到两个部队同时灭的情况。 最后调整: 可见武力100的武将发动兵法的威力是武力0的武将的2倍稍多;而熟练度1000的武将发动兵法的威力是熟练度0的武将的1.5倍。联动武将的伤害值是折半后加入到总的加成伤害中的,而如果出现动画的话,威力将是非常之大的。 另外,由于发动兵法时总的伤害是将基本攻击伤害再加上纯的兵法伤害而形成,使得总伤害看似跟统率、士气等因素有关,然而纯的兵法伤害其实只跟武力和熟练度有关。 另外,仔细看看可发现,发动兵法时防守方形成的伤害被强度乘了两次。因此如果设定200%的话,发动兵法时自己的损失相当于没加强度时的9倍(而普通攻击时只有3倍)!我不知道这是一个Bug还是有意设计的。如果是有意设计的,那么设计者的意图似乎是不提倡在加强度的情况下发兵法?这似乎不太合理,偶猜测也许是个bug。 2、普通攻击的伤害 普通攻击的伤害计算比较复杂,因为关联的因素比较多。下面以部队及城内守兵为例给出计算公式。 变量: LA:攻击部队大将统率; LD:防守部队大江统率; MA:攻击部队士气; MD:防守部队士气; tA:攻击部队阵形参数,tA1:阵形对部队,tA2:阵形对守兵,tA3:阵形守备力; tD:防守部队阵形参数,tD1:阵形对部队,tD2:阵形对守兵,tD3:阵形守备力; SA:攻击部队总士兵数; SD:防守部队总士兵数; D:城池耐久; TA:攻击部队状态; TD:防守部队状态; S:一个未知因子,偶跟踪时总为1。 中间函数符号: F(M):士气的函数; F(tA):阵形攻击参数的函数; F(tD):阵形防守参数的函数; F(T):部队状态的函数; F(L,S,M):统率、士气、兵力、状态及因子S的一个函数。 结果记号: HA:部队对守兵的伤害; HD:守兵对部队的伤害。 计算公式: 部队对部队的计算类似,只不过将阵形参数取为部队的参数,而且计算守备力时没有城防加成一项。以上结果均为不考虑强度的结果,如果需要考虑强度,只需再乘以强度即可。 可见,影响部队攻防能力的己方因素是大将统率、士气、兵力、阵形、状态,真正的伤害还需要由对方的因素共同决定。 可以看出,城防对守兵的守备力影响是较大的,F(tD3)中通常后面一项不会超过300,而城防高时,前面一项数值较大,对方与起到很大作用。但是城防并非越高越好,如果太高,HA中负值太大,远远小于-900,则攻击方伤害将大大增加。不妨把城防改成5、6K试试看就明白。最好的城防值应该是使HA中第一大项为1,近似估计D*2/5=700时,防御较好,即D取略低于2000的数值有利于防御。 城内的守兵的阵形是按照“正阵”的参数取的,因此提高正阵的参数有利于增强守兵的能力。 3、兵法抵挡概率 当防守部队中某一武将的熟练度比发动兵法的武将熟练度高时,存在一定的概率抵挡住兵法。 变量: EA:发动武将的熟练度; ED:防守部队中的最高熟练度。 结果: P:抵挡概率 可见,如果熟练度为3000,那么一定可以抵挡1000点及以下的武将的兵法。这一点很早之前我就提到过,不过当时没有研究公式。但有一个例外:象兵阵无法抵挡步兵兵法。 4、兵法动画(double)概率 变量: E:发动武将的熟练度 P:出现动画的概率 5、发动兵法后能力上升的概率 发动兵法的武将有一定概率可提升能力数值。这个概率是固定数字:P=3%。 6、兵法联动概率 相性相近的话比较容易联动。具有特殊列传号的武将也比较容易联动。发生联动的概率: 可见,对于一般武将,联动概率最大为75%;有特殊联动的,则为95%。另外,似乎义兄弟也是有影响的,偶没有仔细察看代码。 特殊联动的列传号码放在61???地址附近。 狙击: 发动弓箭类或者兵法时,可能发生狙击。狙击的概率按照如下公式计算: 狙击概率=(弩兵熟练度/100)2/2+阵形加成 阵形加成只有箕形阵才有,为30%,其他均为0。 注意狙击的概率只与弩兵的熟练度有关,因此即便弓骑兵练到1000点,而弩兵熟练不足100,不选畸形阵的话,一定不会发生狙击。综合起来考虑,个人认为弩兵才是攻城的主力。 四、战术AI分析 本来偶是很希望能够做一些补丁以改进San9的,但是通过对AI的分析偶打消了这个念头,还是等新版本吧。这个游戏的AI是如此之低劣,或许根本不配叫作AI,仅仅是一些定死的规则,详细的代码偶也没有继续分析了。 一)、防守战术AI 战术AI的体现主要在如何组队上。具体又包括如何选人、如何选兵法、如何选阵形、如何分配兵力。偶大致看了一下,所有这些代码全部按照死定的规则来选择,根本不会依据目标的战力以及目前己方的战力(武将、兵力、兵法等因素)来动态搜索选择。偶预期的AI函数应该是根据各种因素的综合从一定的状态空间中采用搜索函数选择最佳方案,然而实际程序代码却只是死规则的体现。简单看了一下防守阵形的选择,每种阵形都有特定的选择规则,大概的规则有如下一些: 1、 如果部队中没有任何步兵、骑兵、弓兵、弩兵兵法,则选择方圆阵; 2、 如果骑兵兵法数目占优且单挑占优(好像还有一些限制),则锋矢阵; 3、 如果骑兵+弓骑兵占优,好像会考虑锥形阵; 4、 如果弓骑兵+弩兵占优,会考虑箕形阵。 …… 总之,对几种阵形作了死规则限定,只有满足规则时才选择它们。如果不满足任何一种规则,则随机选择某种阵形。 选人、选兵法的函数就在选阵函数前面,未作分析,相信也大致类似。 二)、攻击战术AI 未分析,相信也是死规则,没有综合评估函数与搜索函数。 一句话,这个游戏没有AI,只有死规则。个人认为不值得在此基础上做Mod。
|