也试着写游戏:四路顶
这是一篇旧文,其中的内容可能已经过时。
我学会的第一种棋,不是中国象棋也不是五子棋,而是家乡代代相传的土棋。它叫四路顶,听说在山东叫走四棋儿。
4×4 的棋盘,双方各执四子摆满相对的边;双方轮流走棋,每步一格。若一方通过走棋,摆成纵横相邻两个子,且一颗对方的子与这两个子相邻、这三个子的两端位置没有别的子,就把对方的那颗棋子吃掉了。
下面展示了空心子被吃的例子,方向键表示上次的移动。
┌─┬─↓─┐ ┌─↓─┬─┐
├─●─●─○ ├─●─●─○
├─┼─┼─┤ ├─●─┼─┤
└─┴─┴─┘ └─○─┴─┘
而下列情况均不构成吃棋:
┌─┬─↓─┐ ┌─↓─┬─┐ ┌─┬─↓─┐
●─●─●─○ ●─●─○─○ ●─●─○─┤
├─┼─┼─┤ ├─┼─┼─┤ ├─┼─┼─┤
└─┴─┴─┘ └─┴─┴─┘ └─┴─┴─┘
我开发了一个在线对战的版本,支持创建私人房间。服务端用 Node.js 和 socket.io,容器化后托管在 DaoCloud.io 上;客户端是用 canvas 绘制的。
在这个项目里,客户端只负责处理通信和绘图,逻辑处理均在服务端。尽管这个游戏谈不上复杂,但每次开发都会总结出些许经验:
-
socket.io 的数据传递方式是 JSON,而
undefined
和数组中的空位置都会在序列化过程中被转换为null
; -
游戏大体开发完成时,我告知了一些友人,结果不幸被他们找到了注入点。谨记:客户端传来的所有数据都是不可信的。有个经典的例子:
-
适当把方法分离得细小,以便于子类的修改。我在高中时发明了一个变种,用六边形的棋盘,可以让三个人玩。这个类继承下来,只改了三个必改的方法:
class LiuLuDing extends SiLuDing { constructor(room, emitter) { super(room, emitter); } initGrid() { // 设置棋盘 this.grid = [ [0, 0, 0, 0], [null, null, null, null, null], [null, null, null, null, null, null], [1, null, null, null, null, null, 2], [null, 1, null, null, null, null, 2], [null, null, 1, null, null, null, 2], [null, null, null, 1, null, null, 2], ]; } check(i, j) { // 当玩家落子时,告诉程序要检查哪些方向 this.checkByDirection(i, j, 'horizonal'); this.checkByDirection(i, j, 'slash'); this.checkByDirection(i, j, 'backslash'); } getLineOfSeven(i, j, direction) { // 对于每个方向,拿出附近的七个位置的信息用来判断是否吃子 } }
-
对于这种在线多人应用,不能忽略内存回收的问题。JavaScript 的内存回收机制是这样的:在运行时,检测每个对象的被引用数,若这个数为 0,则表明这个对象已无法访问,垃圾回收器就会将其回收。所以当一个对象的生命周期结束后,记得手动清除对其所有的引用。
游戏源码 放在了 Github 上,感兴趣的同学可移步。之后还会加入一些特性,比如成就等等。说不定将来某天会登陆 steam 呢 😀