今年我给自己定了两个目标:不再看教程,直接动手做项目。听起来像所有新年决心一样不靠谱,但至少这两个我打算认真执行。

我想提升后端技能,练习一些之前没接触过的进阶概念,于是决定做一个在线多人游戏。最开始选的是国际象棋,但规划了几分钟走子逻辑和规则后,我意识到光是搞定棋子移动就要花掉大量时间,要么偏离真正的学习目标,要么直接放弃。用现成的验证库?那又失去了从头构建的乐趣。

打开网易新闻 查看精彩图片

需要足够难,让我有点头疼但又不会崩溃。最后我选了Ludo(印度十字戏)。

前端:从棋盘开始

我没有先做落地页、登录或玩家选择,而是直接攻游戏棋盘。先把核心玩法跑通,其他功能后面再插拔。外层布局用Tailwind实现。

接下来是棋子移动的格子。用CSS画网格不难,但怎么追踪棋子位置?我写了一个函数,接收位置参数返回对应样式。代码很长,肯定有更优雅的写法,但能跑就行。每个格子对应棋盘上的固定位置,我把这个函数丢进utils文件夹,取名getCellPosition。

棋子设计好后,用另一个函数设定初始位置。还需要骰子组件。在npm上搜到3d-dice这个3D骰子包,完全匹配需求,但没有类型定义。我本来打算自己写.d.ts文件,但转念一想:我能想到的事,别人肯定也想到了。翻了一遍项目fork,果然找到一个完整带类型的版本。

游戏逻辑:最硬的部分

这是整个项目最难的环节。不想展开讲琐碎细节,只给高层 overview。

我用useReducer当游戏引擎,因为游戏状态多且互相牵扯。初始状态对象包含玩家数组,每个玩家又有棋子数组。设计了三个reducer动作:ROLL_DICE(掷骰)、SELECT_NUMBER(选数字)、MOVE_PIECE(移动棋子)。我的理解是:玩Ludo时,你先掷骰子,心里选一个数字,然后移动棋子——reducer就按这个流程设计。

接下来是状态机……