请给出子力计算调用的顺序以及子力值计算的数学模型
30 2025-10-09 11:39
在ElephantEye(象眼)中国象棋程序中,子力值计算的调用顺序和数学模型如下:
✅ 一、子力值计算调用顺序
子力值计算主要在评估函数中调用,其调用顺序如下:
-
PositionStruct::Evaluate(int vlAlpha, int vlBeta)
↓ -
PositionStruct::Material()
↓ -
遍历棋子,累加
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() ” 里被读取,它根本不是给“精细评估”用的,而是给**“粗略比较”**用的快速子力分值。下面把调用链、代码片段、数学含义一次性说清。
-
调用链(唯一路径)
PositionStruct::Evaluate
→ 若开启快速剪裁(知识关闭或边界已够紧)
→ PositionStruct::Material() // 仅在此处
→ 累加 cnSimpleValues[pc] // 唯一用到的地方
→ 若开启快速剪裁(知识关闭或边界已够紧)
→ PositionStruct::Material() // 仅在此处
→ 累加 cnSimpleValues[pc] // 唯一用到的地方
-
代码证据(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);
}
{
int vl = 0;
for (int pc = 16; pc < 48; pc++) // 0-15 是空槽
if (ucsqPieces[pc] != 0) // 棋子还在盘上
vl += cnSimpleValues[pc]; // ← 就是这里
return SIDE_VALUE(sdPlayer, vl);
}
-
数值表含义(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
/* 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 分算,没有逐级加分)
-
数学模型
Material = Σ cnSimpleValues[pc] , pc∈{16…47}
返回的是“红方减去黑方”的带符号总量(因为最后有 SIDE_VALUE)。
返回的是“红方减去黑方”的带符号总量(因为最后有 SIDE_VALUE)。
-
使用场景总结
-
只在 Material() 里出现,精细评估表 PreEval.ucvlXXX 完全不用它。
-
用于
– 快速判断“子力悬殊”是否足以提前剪枝;
– 知识关闭时(Search.bKnowledge == false)作为退化的评估值;
– 和棋或重复局面检测时的辅助量。
换句话说:
cnSimpleValues 是“粗秤”,PreEval 表才是“精秤”。
cnSimpleValues 是“粗秤”,PreEval 表才是“精秤”。
Material()和Evaluate(vlAlpha, vlBeta)在search.cpp中Evaluate中的作用
在
下面把它在搜索树里的作用、调用时机、取舍逻辑一次性说清。
search.cpp
中,Evaluate(const PositionStruct &pos, int vlAlpha, int vlBeta)
是一个内联包装函数,它决定了到底用“粗评”还是“精评”,而 Material()
只是其中可能走的一条短路分支。下面把它在搜索树里的作用、调用时机、取舍逻辑一次性说清。
-
调用链(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; // 防止“完全相等”被误判为和棋
}
-
在搜索中的实际用途
表格
复制
搜索阶段 | 调用 Evaluate 的目的 | 知识开关影响 |
---|---|---|
Quiescence 搜索 SearchQuiesc() |
叶节点没吃子可走了,返回静态估值 | 若 bKnowledge==false 直接用 Material() 返回,跳过所有形状计算 |
深度已达极限 LIMIT_DEPTH |
来不及展开,用静态估值顶上去 | 同上 |
内部迭代加深(IID) SearchPV() |
拿到一个快速估值做窗口 | 同上 |
唯一走法检验 SearchUnique() |
只需粗略比较,不必精评 | 同上 |
-
数学含义与“短路”逻辑
-
粗评
vl = Material() = 子力差(半兵单位)
计算量 ≈ 48 次加法,O(1)。 -
精评
vl = pos.Evaluate(vlAlpha, vlBeta)
会依次累加-
Material()(再次调用,但编译器会内联)
-
AdvisorShape()(士象形状罚分)
-
StringHold()(车马炮牵绳奖励)
-
RookMobility()(车路机动性)
-
KnightTrap()(马陷阱罚分)
总计算量 ≈ 粗评 × 10~30 倍。
-
-
边界短路
在Evaluate()
内部,精评函数本身还会用EVAL_MARGIN1~4
做
“如果当前值已经离边界很远,就提前返回”的剪枝,进一步节省工时。
-
一句话总结
Material()
是“应急秤”,Evaluate()
是“精确秤”;搜索树里每一次静态估值都必须经过
Evaluate()
这个分发器,它根据全局开关
Search.bKnowledge
决定到底用哪把秤,既保证深度节点够准,又保证浅层或高速模式够快。
全部评论