请给出子力计算调用的顺序以及子力值计算的数学模型

30 2025-10-09 11:39

在ElephantEye(象眼)中国象棋程序中,子力值计算的调用顺序和数学模型如下:

✅ 一、子力值计算调用顺序

子力值计算主要在评估函数中调用,其调用顺序如下:
  1. PositionStruct::Evaluate(int vlAlpha, int vlBeta)
  2. PositionStruct::Material()
  3. 遍历棋子,累加 PreEval.ucvlWhitePieces[pt][sq]PreEval.ucvlBlackPieces[pt][sq]

✅ 二、子力值计算的数学模型

🔹 1. 子力值来源

每个棋子在不同位置、不同阶段(中局/残局)都有一个预计算子力值,存储在:
cpp
 
复制
PreEval.ucvlWhitePieces[pt][sq]
PreEval.ucvlBlackPieces[pt][sq]
 
这些值由以下因素线性插值得到:

🔹 2. 插值模型(中局 ↔ 残局)

对于每个棋子类型 pt 和位置 sq,其最终子力值为:

value(sq,pt)=TOTAL_MIDGAME_VALUEmidValue×midWeight+endValue×endWeight​

其中:
  • midValue = cucvlXXXMidgame[sq](中局值)
  • endValue = cucvlXXXEndgame[sq](残局值)
  • midWeight = nMidgameValue(当前中局权重)
  • endWeight = TOTAL_MIDGAME_VALUE - nMidgameValue(残局权重)

🔹 3. 中局权重计算

nMidgameValue=PopCnt32(棋子总数按类型加权)

权重如下:
表格
 
复制
棋子类型 权重
将/帅 0(不计)
士/象 1
马/炮 3
6

nMidgameValue=ROUND(TOTAL_MIDGAME_VALUE2⋅TOTAL_MIDGAME_VALUE−current_value​⋅current_value)


✅ 三、总结:子力值数学模型

pieceValue(sq,pt)=TMidgameTab[sq]⋅nMidgameValue+EndgameTab[sq]⋅(T−nMidgameValue)​​

其中:
  • T=TOTAL_MIDGAME_VALUE
  • nMidgameValue 是当前局面中局权重
  • 表格如 cucvlKnightMidgame, cucvlKnightEndgame 等已预定义

✅ 四、举例:马在中局和残局的子力值插值

假设:
  • 马在 sq = 0x53
  • cucvlKnightMidgame[0x53] = 103
  • cucvlKnightEndgame[0x53] = 100
  • nMidgameValue = 100
  • TOTAL_MIDGAME_VALUE = 200
则:

value=200103⋅100+100⋅100​=20010300+10000​=101.5⇒101


如需我帮你生成一个子力值表格可视化某一类棋子的中局/残局值热力图,可以继续提问。

 

在上述什么步骤调用了cnSimpleValues里面的值?怎么用的?什么含义?

cnSimpleValues 只在 “ Material() ” 里被读取,它根本不是给“精细评估”用的,而是给**“粗略比较”**用的快速子力分值。
下面把调用链、代码片段、数学含义一次性说清。

  1. 调用链(唯一路径)
PositionStruct::Evaluate
→ 若开启快速剪裁(知识关闭或边界已够紧)
 → PositionStruct::Material()    // 仅在此处
  → 累加 cnSimpleValues[pc]    // 唯一用到的地方

  1. 代码证据(position.cpp)
int PositionStruct::Material(void) const
{
int vl = 0;
for (int pc = 16; pc < 48; pc++) // 0-15 是空槽
if (ucsqPieces[pc] != 0) // 棋子还在盘上
vl += cnSimpleValues[pc]; // ← 就是这里
return SIDE_VALUE(sdPlayer, vl);
}

  1. 数值表含义(position.cpp)
const int cnSimpleValues[48] = {
/* 0–15 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 空槽
/16–31 红/20, 2,2, 2,2, 5,5, 10,10, 5,5, 2,2,2,2,2,
/32–47 黑/20, 2,2, 2,2, 5,5, 10,10, 5,5, 2,2,2,2,2
};
    K  A A B B N N R R C C P P P P P
单位是“半兵”(1 分 ≈ 一个卒子)。
  • 车 10 分
  • 马炮 5 分
  • 士象 2 分
  • 将帅 20 分(仅用于和棋判断或极粗略估值)
  • 卒 2 分(过河前、后都按 2 分算,没有逐级加分)

  1. 数学模型
Material = Σ cnSimpleValues[pc] , pc∈{16…47}
返回的是“红方减去黑方”的带符号总量(因为最后有 SIDE_VALUE)。

  1. 使用场景总结
  • 只在 Material() 里出现,精细评估表 PreEval.ucvlXXX 完全不用它
  • 用于
     – 快速判断“子力悬殊”是否足以提前剪枝;
     – 知识关闭时(Search.bKnowledge == false)作为退化的评估值;
     – 和棋或重复局面检测时的辅助量。
换句话说:
cnSimpleValues 是“粗秤”,PreEval 表才是“精秤”。

 

Material()和Evaluate(vlAlpha, vlBeta)在search.cpp中Evaluate中的作用

search.cpp 中,Evaluate(const PositionStruct &pos, int vlAlpha, int vlBeta) 是一个内联包装函数,它决定了到底用“粗评”还是“精评”,而 Material() 只是其中可能走的一条短路分支
下面把它在搜索树里的作用、调用时机、取舍逻辑一次性说清。

  1. 调用链(search.cpp 里真正出现的只有这一处)
cpp
 
复制
inline int Evaluate(const PositionStruct &pos, int vlAlpha, int vlBeta) {
    int vl;
    vl = Search.bKnowledge ? pos.Evaluate(vlAlpha, vlBeta)   // 精评
                           : pos.Material();                // 粗评
    return vl == pos.DrawValue() ? vl - 1 : vl;             // 防止“完全相等”被误判为和棋
}
 

  1. 在搜索中的实际用途
表格
 
复制
搜索阶段 调用 Evaluate 的目的 知识开关影响
Quiescence 搜索 SearchQuiesc() 叶节点没吃子可走了,返回静态估值 bKnowledge==false 直接用 Material() 返回,跳过所有形状计算
深度已达极限 LIMIT_DEPTH 来不及展开,用静态估值顶上去 同上
内部迭代加深(IID) SearchPV() 拿到一个快速估值做窗口 同上
唯一走法检验 SearchUnique() 只需粗略比较,不必精评 同上

  1. 数学含义与“短路”逻辑
  • 粗评
    vl = Material() = 子力差(半兵单位)
    计算量 ≈ 48 次加法,O(1)
  • 精评
    vl = pos.Evaluate(vlAlpha, vlBeta)
    会依次累加
    1. Material()(再次调用,但编译器会内联)
    2. AdvisorShape()(士象形状罚分)
    3. StringHold()(车马炮牵绳奖励)
    4. RookMobility()(车路机动性)
    5. KnightTrap()(马陷阱罚分)
      总计算量 ≈ 粗评 × 10~30 倍
  • 边界短路
    Evaluate() 内部,精评函数本身还会用 EVAL_MARGIN1~4
    “如果当前值已经离边界很远,就提前返回”的剪枝,进一步节省工时。

  1. 一句话总结
Material() 是“应急秤”,Evaluate() 是“精确秤”;
搜索树里每一次静态估值都必须经过 Evaluate() 这个分发器
它根据全局开关 Search.bKnowledge 决定到底用哪把秤,
既保证深度节点够准,又保证浅层或高速模式够快

 

 

 

 

 

 

 

全部评论

·