zobrist哈希的增量更新

本文介绍了如何通过增量计算的方法优化FoolGo围棋程序中的Zobrist哈希值计算,避免了每次落子时对整个棋盘状态的重复计算,显著提升了性能。

  随着对FoolGo优化的进行,每次落子时重复计算棋盘的zobrist哈希值成为眼见的性能热点之一,须要修改成增量计算。

   需要一个结构来表示落子前后,棋盘状态的变化:

class BrdChange
{
public:
    template <typename T>
    struct Change {
        T origin_, now_;
    };
    struct Pair {
        PointIndex indx_;
        Change<Point> pnt_;
        ...
    };
    ...
private:
    Change<PointIndex> ko_indx_;
    Change<PlayerColor> last_player_;
    std::vector<Pair> indxs_;
};

  则可为Zobhasher类增加接口:

HashKey GetHash(HashKey hash, const BrdChange &chng) const;

  计算每单位变化的哈希增量的函数:

template <typename T, typename GetHash>
inline static HashKey
HashChange(const BrdChange::Change<T> &chng, const GetHash &get)
{
    return get(chng.origin_) ^ get(chng.now_);
}

  则易实现新增的GetHash接口:

template <BoardLen BOARD_LEN>
HashKey
ZobHasher<BOARD_LEN>::GetHash(HashKey hash, const BrdChange &chng) const
{
    auto getko = [this](PointIndex ko_indx) {
        return (ko_indx == BoardInGm<BOARD_LEN>::NONE) ?
            noko_hash_ : ko_hash_[ko_indx];
    };
    auto getplayer = [this](PlayerColor color) {
        return player_hash_[color];
    };
    HashKey r = hash ^ HashChange(chng.KoChng(), getko) ^
                HashChange(chng.LastPlayerChng(), getplayer);

    for (auto pair : chng.PointsChng()) {
        PointIndex pair_indx = pair.indx_;
        auto getpnt = [this, pair_indx](Point pnt) {
            return board_hash_[pair_indx][pnt];
        };
        r ^= HashChange(pair.pnt_, getpnt);
    }

    return r;
}

  优化后的哈希计算开销小了很多:

  PS:依然在杭州找工作中……

转载于:https://www.cnblogs.com/qswang/archive/2012/12/23/2829970.html

.程序集 血战麻将AI .程序集变量 牌型缓存, 哈希表 .程序集变量 决策库, 决策记录, 数组 .子程序 __启动窗口_创建完毕 加载牌型库("mahjong_patterns.dat") 初始化决策库() ' ====== 核心算法模块 ====== .子程序 换三张决策, 文本型 .参数 手牌, 麻将牌, 数组 .局部变量 花色分布, 整数型, , "3" ' 万/条/筒 .局部变量 总牌数, 整数型 .局部变量 熵值, 小数型 .局部变量 i, 整数型 总牌数 = 取数组成员数(手牌) .计次循环首 (总牌数, i) .判断开始 (手牌[i].类型 == "万") 花色分布[1] = 花色分布[1] + 1 .判断 (手牌[i].类型 == "条") 花色分布[2] = 花色分布[2] + 1 .判断 (手牌[i].类型 == "筒") 花色分布[3] = 花色分布[3] + 1 .默认 .判断结束 .计次循环尾() ' 计算熵值 (牌型混乱度) 熵值 = 0 .计次循环首 (3, i) .如果真 (花色分布[i] > 0) 概率 = 花色分布[i] / 总牌数 熵值 = 熵值 - 概率 * 求对数(概率, 2) .如果真结束 .计次循环尾() ' 决策逻辑 .判断开始 (熵值 < 1.2) ' 牌型集中 返回 保留主花色(手牌, 取数组最大值索引(花色分布)) .判断 (熵值 > 1.8) ' 牌型分散 返回 弃边缘牌(手牌) .默认 返回 弃中间牌(手牌) ' 平衡策略 .判断结束 ' ====== 牌型分析引擎 ====== .子程序 分析牌型, 小数型 .参数 手牌, 麻将牌, 数组 .局部变量 花色分布, 整数型, , "3" .局部变量 主花色, 整数型 .局部变量 清一色概率, 小数型 .局部变量 对子数, 整数型 .局部变量 i, 整数型 ' 计算花色分布 .计次循环首 (取数组成员数(手牌), i) .判断开始 (手牌[i].类型 == "万") 花色分布[1] = 花色分布[1] + 1 .判断 (手牌[i].类型 == "条") 花色分布[2] = 花色分布[2] + 1 .判断 (手牌[i].类型 == "筒") 花色分布[3] = 花色分布[3] + 1 .默认 .判断结束 .计次循环尾() 主花色 = 取数组最大值索引(花色分布) 清一色概率 = 花色分布[主花色] / 14 * 0.85 ' 经验系数 ' 小七对检测 手牌排序(手牌) ' 按牌值排序 对子数 = 0 i = 1 .循环判断首 () .如果真 (i < 取数组成员数(手牌)) .如果真 (手牌[i].值 == 手牌[i+1].值) 对子数 = 对子数 + 1 i = i + 2 ' 跳过对子 .否则 i = i + 1 .如果真结束 .如果真结束 .循环判断尾 (i <= 取数组成员数(手牌)) .局部变量 小七对概率, 小数型 小七对概率 = 对子数 / 7 * 0.9 ' 经验系数 ' 胜率预测公式: $P_{win} = 0.7 + 0.2P_{清一色} + 0.15P_{小七对} + 0.1\frac{N_{有效牌}}{14}$ .局部变量 有效牌数, 整数型 有效牌数 = 计算有效牌数(手牌, 主花色) .局部变量 预测胜率, 小数型 预测胜率 = 0.7 + 0.2 * 清一色概率 + 0.15 * 小七对概率 + 0.1 * (有效牌数 / 14) 返回 预测胜率 ' ====== 决策树引擎 ====== .子程序 生成决策, 文本型 .参数 当前牌局, 牌局状态 .局部变量 动作表, 小数型, , "4" ' 出牌/碰/杠/胡 .局部变量 胜率, 小数型 胜率 = 当前牌局.胜率 ' 动作评分公式: $S_i = w_i \times (P_{win} + C_i)$ 动作表[1] = 0.6 * (胜率 + 出牌收益(当前牌局)) ' 出牌 动作表[2] = 1.2 * (胜率 + 碰牌收益(当前牌局)) ' 碰牌 动作表[3] = 1.5 * (胜率 + 杠牌收益(当前牌局)) ' 杠牌 动作表[4] = 2.0 * (胜率 + 胡牌收益(当前牌局)) ' 胡牌 ' 选择最优动作 .局部变量 最佳动作, 整数型 最佳动作 = 取数组最大值索引(动作表) .判断开始 (最佳动作 == 1) 返回 "出牌:" + 选择出牌(当前牌局.手牌) .判断 (最佳动作 == 2) 返回 "碰牌" .判断 (最佳动作 == 3) 返回 "杠牌" .判断 (最佳动作 == 4) 返回 "胡牌" .默认 返回 "出牌" .判断结束 ' ====== 学习系统 ====== .子程序 更新模型 .参数 对局结果, 逻辑型 ' true=赢 .参数 决策路径, 文本型 .局部变量 学习率, 小数型 学习率 = 0.05 ' 动态调整系数 .如果真 (对局结果) 决策库.更新胜率(决策路径, 学习率) .否则 决策库.更新胜率(决策路径, -学习率) .如果真结束 ' 权重衰减公式: $w_{new} = w_{old} \times (1 - \lambda) + \Delta w$ .计次循环首 (取数组成员数(决策库), i) 决策库[i].权重 = 决策库[i].权重 * 0.98 + 决策库[i].增量 .计次循环尾() ' ====== 辅助函数 ====== .子程序 保留主花色, 文本型 .参数 手牌, 麻将牌, 数组 .参数 主花色索引, 整数型 ' 实现略:保留主花色牌,弃掉其他花色牌 .子程序 弃边缘牌, 文本型 .参数 手牌, 麻将牌, 数组 ' 实现略:优先弃掉字牌和边张(1,9) .子程序 计算有效牌数, 整数型 .参数 手牌, 麻将牌, 数组 .参数 主花色, 整数型 ' 实现略:计算能组成顺子、刻子的有效牌数量 .子程序 出牌收益, 小数型 .参数 当前牌局, 牌局状态 ' 实现略:根据当前牌局计算出牌收益 ' ====== 缓存优化 ====== .子程序 获取决策, 文本型 .参数 牌型指纹, 文本型 .如果真 (牌型缓存.存在(牌型指纹)) 返回 牌型缓存[牌型指纹] .否则 .局部变量 结果, 文本型 结果 = 计算决策(牌型指纹) 牌型缓存.添加(牌型指纹, 结果, 600) ' 缓存10分钟 返回 结果 .如果真结束 这个程序还有要改进的地方吗 ' ====== 多线程处理 ====== .子程序 异步决策 .参数 当前状态, 牌局状态 .局部变量 线程ID, 整数型 线程ID = 启动线程(&计算最优决策, 当前状态) 等待线程(线程ID, 500) ' 超时500ms
最新发布
08-27
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值