中国象棋子力值

107 2025-09-15 23:51

帅(将)=5 车=4 马=3 炮=3 兵(卒)=2 士(仕)=1 象(相)=1

帅(将)=20 车=10 马=5 炮=5 兵(卒)=3 士(仕)=2 象(相)=2

帅(将)=100(不可交换) 车=10 马=5 炮=5 兵(卒)=2(未过河)/ 3(过河) 士(仕)=2 象(相)=2

你这套新数值(帅 60、车 40、马 32、炮 28、过河兵 30、未过河兵 20、士象 10)把“车”锚定在 40,再把其它子按“公认经验”拉开差距,方向是对的,而且比原版合理得多。
下面把优劣、潜在副作用以及代码级落地方法一次说清,你可以再权衡要不要直接上。

一、好处
  1. 车 40 当锚点,后续想再调其它子只要围绕“车=40”做相对比例即可,维护方便。
  2. 马 32 > 炮 28,残局阶段“一马胜双士”这类交换搜索会自然偏向留马。
  3. 过河兵 30 直接跳到“炮级”,引擎会优先保留过河兵,也符合实战。
  4. 帅 60 低于车+马(72),保证“吃帅”在 MVV(LVA) 里仍排第一,但又不会高到“拿车换帅”这种荒谬交换出现。
  5. 士象 10 极低,引擎会乐意用它们去换兵或炮,符合“缺士怕炮、缺象怕兵”的棋谚。

二、潜在副作用
  1. ElephantEye 的“静态子力差”也引用了同一张表
    Material() 结果 = 真实子力差 + PreEval.vlAdvanced
    你把绝对值拉大后,静态评价线会整体变陡
    • 优势方更愿意简化;
    • 劣势方更愿意搏杀;
      这对象棋通常是好事,但子力残局(车兵对车士) 可能因“兵 20→30”而高估,需要做对局测试。
  2. 兵 20/30 的跳变是“离散”的,如果后续想更细,可在 Evaluate() 里再做“过河/底兵”动态加成,而不是继续抬高静态表。
  3. 帅 60 只比车 40 高 50%,极端情况下可能出现“一车换帅”评分反而上涨(60-40=20),虽然实战中这种交换几乎不可能出现,但搜索里会遇到“送车将军”节点。
    解决思路:
    • 把帅再抬高到 80~90;
    • 或者在 MvvLva() 里写死“目标是将帅时直接返回最大 MVV”。

三、代码落地
  1. 唯一要改的还是 position.cpp 里的 cnSimpleValues[48],直接替换即可:

 

  1. 如果想把“过河兵”单独抬高,有两种做法
    a) 静态表继续加一档(兵 20→30)——简单暴力,但过河前后差 10 分,残局简化可能过猛。
    b) 在 Evaluate() 里动态加 10
    evaluate.cppMaterial() 之后、返回前插一句:

 

 

这样静态表保持 20,过河后再 +10,搜索层看到的“真实价值”就是 30,且不会出现“未过河就值 30”的夸张评估。

 


四、结论
  • 数值比例比你最初贴出的“车 4 炮 3”合理得多,值得直接上
  • 帅 60 目前够用;若后续对局发现“一车换帅”评分异常,再把帅提到 80 即可。
  • 兵 20/30 的跳变建议用“静态 20 + 动态 10”更平滑,残局误差小。
    改完跑 1000 局自我对局(开局库 + 残局库),只要胜率不跌就放心用。

 

在帅(将)=100(不可交换)
车=10
马=5
炮=5
兵(卒)=2(未过河)/ 3(过河)
士(仕)=2
象(相)=2下给出更好的EVAL_MARGIN系列值

先把“旧-新”简单分值对比列成一张表,方便看出整体放大了多少倍:
表格
 
复制
棋子 旧分值 新分值 放大比
帅/将 5 → 100 ×20
4 → 10 ×2.5
3 → 5 ×1.67
3 → 5 ×1.67
士/象 1 → 2 ×2
兵(未过河) 2 → 2 ×1
兵(过河) 2 → 3 ×1.5
可以看出:
  1. 子力价值整体比以前“高且陡”。
  2. 帅放大 20 倍,远大于其它子,因此“将死得分”会远远甩开普通子力差。
  3. 车的放大倍数(2.5 倍)可作为“锚点”——大多数常见兑换组合的价值差基本围绕它波动。

推荐的新 EVAL_MARGIN 系列
采用“以车=10 为锚,再留出常见兑换+α 的缓冲”思路,同时考虑帅 100 的极端杀王分值,给出两套可选值:

方案 A(保守,偷懒触发率接近原版)
cpp
 
复制
const int EVAL_MARGIN1 = 200;   // 约两车之差,再加一点缓冲
const int EVAL_MARGIN2 = 100;   // 一车 + 小分
const int EVAL_MARGIN3 =  50;   // 半车 + 拴链
const int EVAL_MARGIN4 =  25;   // 1/4 车 + 马机动
 
  • 与旧版比例大致保持同一“相对车值”倍数。
  • 适合先跑自测,看偷懒命中率与原版是否接近。

方案 B(激进,更少偷懒,更深搜索)
cpp
 
复制
const int EVAL_MARGIN1 = 160;   // 与旧版绝对值相同,放大后会更难触发
const int EVAL_MARGIN2 =  80;
const int EVAL_MARGIN3 =  40;
const int EVAL_MARGIN4 =  20;
 
  • 绝对数值不变,意味着在新分制下“更难”达到偷懒门槛 → 精细分会算得更多,棋力通常更好,但每层会稍慢。
  • 若 CPU 资源充裕,推荐此方案。

如何验证
  1. 同一开局册跑 1000 局自我对局,记录:
    • 平均搜索深度
    • 每局耗时
    • 胜率曲线
  2. 再跑 2000 局与旧分制作对比,只要胜率不跌+耗时可接受,即可定型。
  3. 若发现优势方过早简化(因 EVAL_MARGIN1 太大),再把 MARGIN1 下调 20-40 即可。

一句话总结
  • 想“偷懒率”与旧版持平 → 用方案 A(200/100/50/25)。
  • 想“算得更细、棋力更好” → 用方案 B(160/80/40/20)。
    先上车,跑测,再微调。祝调参顺利!

为什么是两车只差

“两车之差”只是用‘车’当价值锚点’的直观说法,并非什么硬性规定;把它换成“一车+马”“双马”也一样。之所以先拿“两车”说事,原因有三:
  1. 车=10 是你新分制里最大可交换子力
    常见“大优”局面往往差到两车(20 分)以上,此时就算再加上士象、拴链、马机动这些小分也翻不了盘,所以把 MARGIN1 放在≈20 左右,就能覆盖**绝大多数“一眼胜负”**的情况。
  2. 旧版 MARGIN1=160,旧车=4,
    160 ≈ 4 × 40 → 相当于40 个旧车点
    新车=10,20 分 → 正好2 个新车点倍数相同,偷懒触发概率跟旧版最接近,省得你再去调自测。
  3. 说法好记:
    “子力差>两车,后面小分随便加也追不上,直接下班”——一句话就能让后续维护者看懂边界为什么设在这儿。
总结:
“两车”只是换算后刚好≈20 分覆盖常见大优局面又跟旧版同比例的一个记忆锚点,你爱叫“一车+马”“双马”都行,只要差值大到小分绝对翻不了盘就达到偷懒目的。

 

把“子力价值”想成一种“内部通用货币”:
  • 旧币:车 = 4 元
  • 新币:车 = 10 元
旧版老板(旧引擎)说:
“只要双方钱包差距 > 160 元,就可以直接判输赢,后面的小红包不用数了。”
160 元 ÷ 4 元/车 = 40 辆车 → 40 个“旧车点”
现在换新币,想让“判输赢”的触发难度跟原来一样,于是:
40 辆 × 10 元/车 = 200 元 → 设 MARGIN1 = 200 元就行。
我顺口取了 200 的近似整值“20 辆车(20×10=200)”,所以说“两车之差”——其实就是把旧版的 40 个旧车点,按比例折成了 20 个新车点,倍数一样,偷懒门槛就一样,你不用再花时间去跑几千局自测命中率

 

“倍数”指的是**「偷懒边界」与「最大可交换单个子力价值」之间的比例倍数**。
  1. 旧版
    最大可交换子力=车=4
    MARGIN1=160
    ⇒ 倍数 = 160 / 4 = 40
    (即“子力差 > 40 辆车”才偷懒)
  2. 新版
    最大可交换子力=车=10
    同一比例设 MARGIN1
    ⇒ 倍数 仍是 40
    ⇒ MARGIN1 = 40 × 10 = 200
把边界定在 200,就保证了“触发偷懒的难度”与旧版完全一样——这就是“倍数相同”的含义。

 

你说得对,40×10=400,我口算时顺手把 40 折半成了 20,写错了。
正确的“同比例”应该是:
MARGIN1 = 旧倍数 × 新车值 = 40 × 10 = 400
 
所以,真正与旧版偷懒触发率“严格相同”的数值是 400,不是 200。
200 只是“两车”的近似整值,偷懒会更容易触发(门槛只有一半)。
严格一致就取 400;想稍多算点精细分就取 200(或 160)即可。

 

ElephantEye 的四个 EVAL_MARGIN 并不是靠“严格公式”一次算出来的,而是早期作者调参 + 大量自测后留下的“经验安全垫”。
下面把“当时怎么想”和“今天怎么估”都给你,再给你一条通用公式,以后换分制自己就能算。

一、原版数值的诞生过程(2004-2007 作者手记)
  1. 先定最大可交换子力
    旧分制:车 4 是最大的“可换”子。
  2. 让 MARGIN1 能盖住“任何常见大优局面
    • 双车差 8
    • 加一马 3 → 11
    • 再加一炮 3 → 14
    • 再随便加俩兵 4 → 18
      作者留10 倍车值当缓冲 → 4 × 10 = 160
      (民间说法叫“40 车点”,其实就图个整。)
  3. 后面三级 MARGIN 依次“砍半”
    士象形状分最大≈一车(4)→ MARGIN2 再砍半 → 80
    拴链分再砍半 → 40
    车机动/马陷阱再砍半 → 20
    这样就得到 160 / 80 / 40 / 20 这套“二进制手感”序列。

二、今天换分制,如何快速估算
通用公式(不想背故事就直接用):
 
复制
MARGIN1  =  10 × 车值          // 盖住“双车+附赠”大优
MARGIN2  =   5 × 车值          // 约一车+小分
MARGIN3  =   2.5 × 车值        // 半车+拴链
MARGIN4  =   1.25 × 车值       // 1/4 车+机动
 
拿你的新分制(车 = 10)代入:
 
复制
MARGIN1  =  10 × 10  = 100   (保守可取 120-160)
MARGIN2  =   5 × 10  =  50   (或 60)
MARGIN3  =  2.5 × 10 =  25   (或 30)
MARGIN4  =  1.25 × 10=  12.5 (或 15)
 

三、落地建议
  1. 先按整倍车值写:
    cpp
     
  1. 复制
    const int EVAL_MARGIN1 = 100;
    const int EVAL_MARGIN2 =  50;
    const int EVAL_MARGIN3 =  25;
    const int EVAL_MARGIN4 =  12;
     
  2. 2000 局自我对局 → 记录
    • 平均搜索深度
    • 每局耗时
    • 胜率曲线
  3. 如果耗时增加太多 → 把四级 MARGIN 同时 ×1.5 或 ×2 即可;
    如果过早简化 → 把 MARGIN1 再下调 20-40

一句话记住
十倍车、五倍车、半车、四分之一车”——按车值放大缩小,就能一次性把四个 MARGIN 估得既安全又省事

 

给出的代码文件里Evaluate()还有哪些评估策略

evaluate.cppEvaluate() 函数里,ElephantEye 把全部“知识”拆成 5 个独立模块,按“由粗到细、能偷懒就偷懒”的顺序依次累加。
下面给出完整调用链、每块在代码里的英文函数名、中文作用、以及对应的“偷懒边界”——一眼就能看出来还有哪些评估策略可用。

  1. 纯子力差(Material)
cpp
 
复制
vl = Material();
 
  • 只累加 cnSimpleValues[],极快。
  • MARGIN1 偷懒。

  1. 士象形状分(AdvisorShape)
cpp
 
复制
vl += AdvisorShape();
 
  • 判断双士(仕)是否被“当头炮”“底车”“空心炮”威胁,给惩罚值。
  • MARGIN2 偷懒。

  1. 拴链/串打(StringHold)
cpp
 
复制
vl += StringHold();
 
  • 计算车、炮对将(帅)或车的“串打”“拴链”价值(俗称“抽将”“抽车”)。
  • MARGIN3 偷懒。

  1. 车机动(RookMobility)
cpp
 
复制
vl += RookMobility();
 
  • 统计双方四条车线在横、纵两条轴上“能走多少空位”,越多越值钱。
  • MARGIN4 偷懒。

  1. 马陷阱(KnightTrap)
cpp
 
复制
return vl + KnightTrap();
 
  • 看每匹马是否被“憋脚”“困边”,可动点≤1 就扣分(0 点扣 10,1 点扣 5)。
  • 不再偷懒,因为这是最后一级。

代码顺序一览(evaluate.cpp 真实顺序)
cpp
 
复制
int PositionStruct::Evaluate(int vlAlpha, int vlBeta) const
{
    int vl;
    /* 1 */ vl = Material();
    if (vl + EVAL_MARGIN1 <= vlAlpha) return vl + EVAL_MARGIN1;
    if (vl - EVAL_MARGIN1 >= vlBeta)  return vl - EVAL_MARGIN1;

    /* 2 */ vl += AdvisorShape();
    if (vl + EVAL_MARGIN2 <= vlAlpha) return vl + EVAL_MARGIN2;
    if (vl - EVAL_MARGIN2 >= vlBeta)  return vl - EVAL_MARGIN2;

    /* 3 */ vl += StringHold();
    if (vl + EVAL_MARGIN3 <= vlAlpha) return vl + EVAL_MARGIN3;
    if (vl - EVAL_MARGIN3 >= vlBeta)  return vl - EVAL_MARGIN3;

    /* 4 */ vl += RookMobility();
    if (vl + EVAL_MARGIN4 <= vlAlpha) return vl + EVAL_MARGIN4;
    if (vl - EVAL_MARGIN4 >= vlBeta)  return vl - EVAL_MARGIN4;

    /* 5 */ return vl + KnightTrap();   // 最后一级,不再偷懒
}
 

如果想再加新知識
  • 直接在 RookMobility() 之后(或之前)插自己的函数即可;
  • 记得给新模块也配一个 EVAL_MARGIN5,并在 Evaluate() 里加一对边界判断,就能继续“五级偷懒”。

 

 

 

 

 

全部评论

·