AI分析攻击路径时,存在几个需要解决问题。 1)此轮分析状态下,Attacker攻击Defender最好使用何种战法?Defender回击时使用何种战法?知道战法才能确定攻击距离,确定距离需要使用unit_stats。 2)已确定Attacker攻击Defender这个攻击要汇入攻击路径集后,为计算更多此路径“分数”参数需要使用battle_context。 3)在可攻击路径中筛选,已确定Attacker攻击Defender这条攻击路径要用于实际攻击,这时为保证距离一使,它须要知道分析时它是使用何样战法(保证距离一致),需要知道分析时的unit_stats。 由此右见,AI要不断使用<已方部队Attacker, 敌方部队Defender>这样的配对。为此引入us缓存,us缓存两个作用: 1)分析过程需要大量生成battle_context。由attack_loc和defender_loc构造battle_context须要耗不少CPU,引入缓存希望降低CPU。 2)决定A--B攻击路径为实际攻击,为保持距离一致,须要知道分析时它是使用何种战法,这战法就从us缓存中找。 us缓存的key和value us缓存中value就是以上的std::pair<unit_stats, unit_stats>。key用什么? 在这里key就是作为关键字进行搜索,这个关键字要能表达std::pair<unit_stats, unit_stats>是何处情况下生成的。生成std::pair<unit_stats, unit_stats>时是已方某个部队 攻击敌方某个部队,很容易让想到是用:std::pair<const unit*, const unit*>,当中两个unit*则是两个部队的指针。 以这种方法是能表达唯一性,能根据它搜到指定std::pair<unit_stats, unit_stats>。但为让us缓存更高命中率,采用std::pair<const unit*, const unit_type*>。 first:指向敌方部队指针。 second:已方攻击兵种指针。 注:攻击兵种指针是个全局不变量,以它为指针是安全的。 把second由具体某个部队指针换为该类部队兵种指针,就是为了提高命中率。这里假定该兵种所有部队对该“部队”使用同一战法。 当然,由于部队五维,三个主将能力不同,同一兵种部队能力也表现出很大差别,但在这里做了假定,假定这些差别可以被忽略。 韦诺源码用的是<map_location, const unit_type*>作为key,map_location是被攻击部队所在格子,unit_type*意义一致。改为不用map_location,是对us缓存会有大量的比较 key操作,以指针比较总比map_location要快。 何时清除us缓存中内容 清除us缓存可以两个时机:1)每轮攻击分析后就清除;2)此次AI整个攻击分析后才清除。 第一种:意味着每轮分析开如时它的battle_context集是空的,而分析时生成的很多battle_context之前可能都已经是构造过了的。重新构造,多耗CPU。 第二种:可尽量少构造balle_context,提高缓存使用率。——但这种方法实用下来不可行,使它不可行的原因就是无法保证缓存中项的有效性。让看几种缓存项无效例子。 1)std::pair<const unit*, const unit*>中second指定单位被击溃,attack_num是有效值,weanpon != NULL,但得到的attack结构却是个无效值,像range是""。原因,unit::~unit被调用,从而该unit内的attacks_被析构。unit_stats中存的是指针而不是结构,而且是指定特定部队内的attacks_,attacks_既已被析构,随之指向的将是不可预料值。 2)std::pair<const unit*, const unit*>中的first变得无效。可能是该部队被消灭了,也可能是编写mod造成。例如mod中这样写,赵云部队一旦被击溃,刘备队就被击溃,这种连带的无效使保证这个有效性,很难。 对于这个无效,归结为是已方部队和敌方部队被击溃后,mod可能会写出各式各样情况,像死后原地复活(这种方式下旧unit指针值和新unit指针值可能就是同一个!),为mod编写上灵活,在这里只好牺牲CPU,使用第一种清除时机。 |
1、根据U和u,找出最优战法时的range; 2、针对U能够动到的range各个格子,找出一个最好的,cur_position记住这个值; 3、以这个最好的形成节拍; A:已方单位U能攻击敌方单位u的条件? Q:U--attack-->u,它是形成节拍,而能不能形成这个节拍,它遵循什么条件? 1:U要能够移动到u附近,站在那里以某种战法攻击到。这个不能“移动到附近”有三情况:1)够不到战法距离。如果U最多只能移动和u隔两格,但又只有range=1战法,此次攻击自然不能凑效。2)已有节拍把能攻击到到位置全占了;3)能攻击到的格子全被站了部队。 2:不值得攻击。例如,此个单位是高等级单位,HP只剩1。 在已知U和u条件下,如何找出最优战法? A:为什么choose_attacker_weapon和choose_defender_weapon实现上很不一样? Q:choose_attacker_weapon是两种战法都要选,可认为它当中是两个for循环(第二个for循环在choose_defender_weapon内执行)。choose_defender_weapon是在给定了攻击方战法后决定出采用哪种防御战法,只有一个for循环。 A:attack_weight和defend_weight意义 Q:attack_weight决定该战法能不能用于攻击(不能攻击并不意味着它就没用了,它可以用于防御),它的值说来只有两个,0:不用于攻击;非零:可用于攻击,但强制认为非零>0,而典型值就是1.0。 defend_weight决定该战法被用于防御使用时可能性,值越大可能性越高。但并不是说它的值最大它就肯定被作为要使用战法,还要看该战法给攻击方造成的损伤。例如 战法A:给攻方造成损伤400,weight(0.4),===》400*0.4 = 160 战法B:给攻方造成损伤80,weight(1),===》80*1 = 80 虽然战法B比战法A的weight高,但效果还是战法A好,结果使用战法A。 A:battle_context的输入和输出 Q:输入:attack_loc,defend_loc,prev_def。为什么要有prev_def?在AI计算多节拍路径时,第N节拍碰到的防御方已不是defend_loc得到的那unit,而已经被之前N-1个节拍攻击后残剩的unit,要用这个combatant进行计算。注:选择什么战法要是进行模拟攻击,根据攻击后状态来决定谁优认劣,而模拟攻击要使用到prev_def。 输出:四个参数,攻击防御各两个。 unit_stat:此次战斗形成的各个参数。要攻击次数,每次损伤值,使用的战法(索引及结构)等等。 combatant:对unit_stat补充值。攻击后剩余HP,结果是否被中毒,是否被缓慢; A:do_attack_analysis中构造battle_context时,为何prev_def置为NULL? Q:原则上说,此个prev_def不能置为NULL,置NULL只有在第一节拍是对的,后面节拍时prev_def不应置为NULL,而是应该反映“N-1次攻击后”的那个状态。 此处置为NULL,是种偷懒作法。 do_attack_analysis能不能拿到pref_def?这是可以的,只要cur_analysis.analyze(...)把分析后的那个pref_def保留下来。但我很纳闷,cur_analysis.analyze(...)函数为何要把所有节拍计算一遍呢,它不保留前面N-1次的计算结果吗?每次都计算N次,是不是太耗CPU了。我认为此处可以优化,优化时再想法抽出prev_def,传给do_attack_analysis。 A:每计算一次battle_context较耗资源,如何减少在这方便CPU消耗? Q:以<attack_loc, defend_loc>构造一个battle_context以对象,要选择战法,模拟攻击,还是要耗一定CPU的。但分析攻击路径可又是要进行多次构造battle_context以确定攻击结果。在此采用对已出来的battle_context开缓存办法。基于方法是:battle_context除了<attack_loc, defend_loc>构造,它还能以一个更快的方式构造,这种方法就是使用<attack_unit_stats, defend_unit_stats>。 缓存形式:std::map<std::pair<map_location,const unit_type *>, std::pair<battle_context::unit_stats, battle_context::unit_stats> > key:<防御方所在格子, 攻击方兵种类型指针> 兵种在内存中存储位置,在游戏一初化后就不能再变,它的指针是唯一的。 value:<attack_unit_stats, defend_unit_stats> 由它们可以较快地生成battle_context。 注:由<attack_unit_stats, defend_unit_stats>方式生成的battle_context,要由这个bc得到两个combatant城要经再经过模拟战斗,因而还是须要prev_def。但是,虽然要prev_def,但这种缓存命中方式还是存在缺陷,那就是两者战法比较是以第一个遇到<map_location,const unit_type *>时决定的,此时的prev_def并不见得是那时的pref_def,这叫造成结果存在一定出入。 缓存存放位置:ai_composite对象内的unit_stats_cache_变量。它在ai_composite::play_turn要被反回时clear。 |
一、analyze_targets
1.1:清空相关缓存 缓存包括us缓存(unit_stats_cache)、城内部队缓存(reside_cache)。
A:为什么要有cur_analysis参数? Q:有些攻击路径不只一个节拍,让此次分析基于它形成多节拍路径。不考虑递归调用,一次do_attack_analysis只是形成一个节拍。例如,给的cur_analysis是空的,那么此次do_attack_anlysis形成是单节拍路径,如果是N节拍,那么此次将形成N+1节拍路径。 注:对路径来说,多一个节拍就意味着又是一条新的路径,因此不考虑递归调用下,一次do_attack_analysis也是形成了一个新路径。 函数主要逻辑
|