主要内容总结:
- 内容概述:作者介绍了自己用 Zig 语言制作象棋引擎的过程,包括象棋引擎的工作原理、在 Zig 语言中的一些思考、在博客中运行引擎的痛苦经历以及最终想法等。
- 尝试运行:在桌面端可查看引擎,若在小屏幕可在桌面加载后玩,目前引擎仍需改进。
象棋引擎工作原理:
- 棋盘表示:从数组表示切换到位棋盘表示,位棋盘利用
u64
的每一位表示一个方格,可快速进行位运算,如计算棋子的移动等,Zig 的std.bit_set.IntegerBitSet
提供了方便的位操作工具。 - 移动生成:滑动棋子的移动生成可能耗时,位棋盘可通过预先计算射线来提高效率,计算射线时利用了 Little Endian Rank File Mapping 规则,将棋盘的索引与
u64
的位对应,通过位运算和掩码操作来确定棋子的可移动位置,Zig 的comptime
特性可在编译时计算射线,提高效率。 - 搜索:搜索移动以找到最佳移动,基本的 Minimax 搜索算法较慢,通过 Alpha-Beta 搜索可提高效率,追踪
alpha
和beta
值来避免搜索不必要的子树。 - 评估:有多种评估象棋局面的方法,简单的方法是计算双方的棋子价值差,还可考虑棋子在棋盘上的位置等因素,以获得更准确的评估。
- 移动排序:对要检查的移动进行排序,优先考虑捕获和避免移动到被攻击的方格,通过给移动打分来确定检查顺序。
- 棋盘表示:从数组表示切换到位棋盘表示,位棋盘利用
- 测试:使用
fastchess
进行测试,fastchess
通过随机位置让两个引擎对战,给出胜负结果,作者的引擎通过 UCI 协议与fastchess
交互。 关于 Zig 的一些思考:
- 内存安全:Zig 起初让人享受无需处理借用检查,但多线程时会出现问题,如未克隆棋盘导致的 UI 渲染错误,以及值传递导致的数组迭代错误等,希望未来能有更好的方式捕获这类简单的内存问题。
- 分配器:Zig 要求显式传入分配器,这有助于选择合适的分配方式,但在传递分配器的过程中也会遇到一些问题,随着使用的增加,作者逐渐减少了动态分配的使用。
- 编译时(Comptime):Comptime 使泛型简单易用,能编写 Zig 代码来处理复杂情况,但会导致 IDE 体验不佳,语言服务器有时难以确定编译时变量的类型。
- 构建系统:Zig 的构建系统简单易用,所有操作都用 Zig 实现,无需处理复杂的构建标志,但作者的构建脚本不够优雅,可进一步优化。
在博客中运行引擎的痛苦经历:
- Emscripten:使用 Emscripten 编译与 C 交互的代码,由于使用了多线程,需要设置许多编译选项,花费数小时才使引擎在 Emscripten 的基本 index.html 包装器中运行。
- Nextjs/react:将运行的 wasm/js/html 代码加载到 Nextjs 静态站点中,遇到了
window
未定义的错误,经过大量搜索和尝试,发现是 webpack 优化导致的问题,最终通过添加特定代码解决,但此过程耗费了大量时间。 - 内容安全头和 GitHub Pages:在 Nextjs 开发服务器上添加了
Cross-Origin-Embedder-Policy
和Cross-Origin-Opener-Policy
安全头,以支持SharedArrayBuffer
在浏览器中运行,但在 GitHub Pages 上无法设置这些头,最终使用 Cloudflare Pages 解决了问题,但此过程也经历了不少挫折。
- 最终想法:去除 wasm 构建的痛苦后,制作象棋引擎是非常有趣的经历,类似于玩一些工程类游戏,Zig 是一种很棒的语言,简单易用且功能强大,希望未来有更好的库生态系统来处理更复杂的项目。
作者还推荐了 Sebastian Lague 的视频,以获取更多关于象棋引擎的详细信息。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。