#!
众所周知,TypeScript 是图灵完备的,因此,只要我们愿意,那当然是可以用它来实现一个象棋程序的。于是我们就快乐地开始了,为了理解方便,我们不考虑性能优化策略,纯粹从功能实现角度去构建。另外,我们尝试用中文来编写这个程序,因为类型运算中需要用到的操作符很少,类型本质上是一种对现实世界的描述,某种程度上算是一种业务描述语言,使用中文也挺好的。
首先我们要先来分析一下,需要实现哪些功能。
象棋的核心功能就是走棋,走棋如果用一个函数来表达,那就是:走棋<当前棋局, 源位置, 目标位置>,因此,这里就涉及两个要素:
比较多的细节会出现在棋子的移动规则上,但是,不重要,都是细节,我们挨个来解释吧。
棋子的话,比较简单,没有什么特别的,只要表达出颜色和种类就可以了:
本节使用的技巧非常简单,就是一个合法性判断而已。
位置就会复杂不少了,主要原因是,在类型系统里面,我们要表示整数及其运算是一件比较麻烦的事情,而偏偏在计算走棋规则的时候,这部分很重要,所以,不得不去做。
定义整数和加减法的部分,在其他文章里可以找到,不在这里叙述了,假定已经存在了一些常用的整数变量,可以得到对于位置信息的表达:
位置比棋子要复杂的原因是,要支持一些运算,比如,判断两个位置是否相同
然后,我们又会需要,对一个位置进行移动,用来定义寻找相对位置的快捷操作,比如,上下左右相邻位置的坐标,需要注意的是,这里要做一下合法性校验,避免越界。
本节使用的规则稍稍复杂一些,但主要复杂度不在这里,而是在定义数字运算的地方。
在棋局这里,我们第一次面对了复杂的状况,整个这部分,需要解决这么一些问题:
我们逐一来解决这些问题。
棋局结构的建模,是与取值操作密切相关的,需要注意到的是,我们并没有使用数字字面量去代表数字,而是自定义了整数类型,这就意味着,取值操作的索引是很复杂的,它无法直接得到。
在我们使用的整数定义中,数字是用一级一级嵌套表达的,当我们表示数字五,其等价形态为:
当我们得到一个数字,并且想要知道它真正表达的是多少,需要去统计从它到零的距离,也就是说,要反向去根据是零吗和前一个数这两个类型属性,一直往前找到零的位置。
有鉴于此,我们选择把棋局也构建为类似形式:
这个看上去非常诡异,但是我们后续对它的取值就很简单了。
本节主要使用了一个 infer 来定义临时变量,然后就是递归去构造了(这里可能是有简便写法的,不过我暂时想不到)。
这一步,我们的目标是实现根据给定位置,从棋局中获取对应的棋子。
先来考虑如何根据一个行号获取指定行,其实很简单,让行号与棋局行链表同步移动就可以了,行号每次减一,行链表每次后移一行,那么,当行号减小到零的时候,行链表指向的就是所要的那行了:
同理,在一行中获取某一列,也是一样的方式:
我们把这两者组合起来,即可得到根据位置查找棋子的类型函数:
本节的主要技巧是类型递归,获取行、单元格的时候,都是递归调用当前的类型函数自身去取值的。
在计算棋子位置关系的时候,我们会需要一些判断规则,这些规则的实现,都可以通过组合现有函数的方式去实现。
一些简单规则我们就不去探讨了,比如说,马的“日”字顶角,象的“田”字顶角,还有马脚,象眼等等,都可以反复组合之前我们做好的计算邻位的四个函数,比如:
我们重点来看一个相对复杂的:统计一条直线上两个棋子之间的棋子数量。这里的麻烦在于,统计个数需要有一个累加计数功能,有趣的是,它仍然可以利用递归。
本节使用了一个小技巧,先把两个坐标排序了一下。这样后续的递归方向可以不需要判断,否则要考虑两个方向。
棋子的移动,本质上很简单,就是把目标位置的棋子内容替换掉就可以了,但是,在 TypeScript 的类型系统里,“替换”是个不太可行的事情,它是 immutable 的,所谓的替换,实际上是创建了新的。
这样,我们就得实现棋局每个层级的拷贝构造操作了,这也不难:
然后,真正复杂的是替换过程中的重新构造,以替换棋局中的某一行为例,这实际上是一个一边遍历,一边根据需要构造的过程,本质上是一个 copy on write 结构,理解了这点之后,写起来就不难了,仍然是递归:
同理,替换单元格也可以写出来:
然后组合两者,就可以得到在棋局上指定位置替换棋子的函数:
两次使用这个替换,即可得到走棋的函数:
本节没有出现新的技巧,只是因为之前的链表结构实现,导致复制起来相对复杂一些。
当我们建立了棋局的走棋规则之后,实际上象棋已经可用了,但为了显示的友好,还需要多做一步。
最基础的渲染单元,是单个的单元格,这个非常简单,直接根据颜色和种类进行两级映射就可以了。
稍微复杂一些的是渲染行,考虑到我们之前把棋局行实现成了单元格链表,这个就需要一个递归了。
接下来,我们需要把每行的渲染结果合并输出,由于在 TypeScript 中,不管是字符串还是数组,都无法控制在 IDE 提示中的换行,因此,我们需要把结果渲染成对象结构。又因为通过字符串形态的整数,无法关联到真实的整数类型,所以需要建立一个辅助索引。
这样,整个棋局就可以被渲染出来了。
本节使用的技巧主要是字符串的拼接,以及对象结构的输出。
在线预览地址: 把鼠标放在最后几行的走棋结果上看提示即可。
本文的意图主要是在不考虑优化的情况下,如何借助类型系统的运算来解构这么一个象棋逻辑。
主体功能都还是比较容易实现的,从目前的实现来看,调用栈太深了,现在走棋已经很难跑出来了,需要优化,比如把数字的计算改成打表,可以优化不少,暂时先不去做它了。(最新消息:在 TypeScript 4.5 中,优化了尾递归深度,可以完美支持了:
特别鸣谢在此文编写过程中提供帮助的引证老师。
开发语言:TypeScript
项目类型:TypeScript 类型系统中国象棋
Star 数:817
这是一套TypeScript 类型系统中国象棋,代码结构清晰,适合开发者学习游戏的客户端渲染、服务端逻辑、场景管理与资源加载。项目涵盖了游戏的核心玩法、界面交互、地图与角色系统等模块,对理解游戏开发的完整流程具有较高的参考价值。
从技术实现角度看,游戏项目的难点在于:游戏循环与状态管理、资源加载与渲染优化、客户端与服务端的协议同步、以及多平台适配。本项目在这些方面提供了可运行的实现,开发者可以在此基础上进行二次开发或学习研究。
1. 下载源码压缩包,解压到本地开发环境。
2. 根据项目类型准备对应的运行环境(Node.js / Go / C# / Unity / Godot 等)。
3. 按照 README 中的步骤配置依赖、数据库和配置文件。
4. 先启动服务端,再运行客户端,即可进入游戏进行测试。
注意:本项目仅供学习研究使用,请勿用于商业或非法用途。
对于想进入游戏开发领域的开发者来说,这套源码是一个很好的入门参考。通过阅读代码,可以学习到游戏的核心玩法实现、场景与资源管理、网络协议设计、以及渲染优化等关键技术点。
同时,项目中的代码组织方式和模块划分,也展示了游戏项目在工程化方面的实践经验,值得开发者深入研究和借鉴。理解一个完整游戏项目的架构,对后续开发其他类型游戏也很有帮助。
本资源来源于开源社区,仅供学习研究和技术交流使用,请勿用于任何商业或违法违规用途。使用本项目产生的一切后果由使用者自行承担。若涉及第三方权益,请联系删除。
| 资源标题 | type-chess - TypeScript 类型系统中国象棋 |
| 所属分类 | 游戏源码 |
| 发布作者 | admin |
| 发布时间 | 2026-10-05 21:41:08 |
| 下载权限 | 免费下载 |
| 附件数量 | 2 个下载链接 |
| 浏览次数 | 455 |
| 讨论数量 | 0 |
| 主题状态 | 正常讨论中 |