需求分析
在很多模拟经营游戏中,科技树是一项重要的内容,其为玩家提供了各项技术与其前后置科技间的拓扑关系。这些科技树在表现形式上和普通树状图很相似,但由于其频繁的分支合并,为科技树的绘制带来困难。因此,我们需要一种简单的方法来绘制科技树。
比如,当用户输入:
<!--为了降低用户的学习门槛,这里的语法参考了wikitext的无序列表-->
*四年计划
**西墙防线
***大西洋壁垒
***东方壁垒
**大空间经济
***亲近罗马尼亚
****整合战时经济
***亲近匈牙利
****整合战时经济
*****组建专员辖区
**自给自足
***帝国高速公路
****德国战时经济
*****组建专员辖区
***煤液化
****合成橡胶
*****增加科研槽
***赫尔曼·戈林工厂
****力量来自欢乐牌汽车
*****增加科研槽
我们期望,输出的样式可以类似于这样:
通过以上构想不难发现,从输入到输出大致要经过这几个步骤:
- 写出无序列表;
- 将这个无序列表加工为普通的树状图;
- 找到树状图中需要合并的分支,将其合并;
- 渲染出最终的成品。
可以看到,上面的步骤中需要将树状图渲染两次,这势必会增加我们工作的难度,为此我们可以将第2、3步调换,再略作调整,最终得到这样的步骤:
- 写出无序列表,将其加工为易于计算机处理的数据;
- 找到原始数据中需要合并的分支,并将其合并;
- 渲染出最终的成品。
然后我们逐步研究程序的实现细节。
明确实现方法
原始字符串的格式要求
虽然之前我们已经大概用wikitext写了一份草稿,并且很聪明地(?)顺便采用了将无序列表中内容相同的分支标记为需要合并的分支这样的方法,但随着研究的深入,我们逐渐会发现这样的问题:
有些科技树中的项具有复杂的结构,这将导致誊写需要合并的分支时异常麻烦,也为原始字符串的可读性造成负面影响。
比如在游戏《戴森球计划》中,科技树上的『冶炼提纯科技』至少应有以下内容:
***冶炼提纯<br/>研究消耗:{{图标|电磁矩阵|100}},解锁物品:{{图标|硅石}}{{图标|高能石墨}}{{图标|高纯硅块}}
解决起来也不麻烦,我们可以把科技树中项的内容分成 名称 和 正文 两部分,用名称来说明项的身份,这样我们在誊写需要合并的项时就会更轻松些。对此,我们可以要求原始字符串中要用“|”来分割名称和正文,名称在前,正文在后。
还是上面的例子,按我们刚刚设计的新语法的规范,它应该是这样子的:
***冶炼提纯 | 研究消耗:{{图标|电磁矩阵|100}},解锁物品:{{图标|硅石}}{{图标|高能石墨}}{{图标|高纯硅块}}
如果后续有什么指向这个科技的分支,那么在那儿它就可以被写成这样:
***冶炼提纯
另外的问题是用户可能在同名分支(需要合并的分支)后接上不同的子枝,这种届时我们直接报错就好。
树状图的渲染方法
在web网页中进行树状图排版的可能的方案着实不少,可惜大部分囿于本人的能力统统不能实现。在我对此苦恼的时候,无意间发现了缺氧wiki中绘制他们的科技树的解决之道:
使用CSS中的grid属性来进行树状图中框和线的绘制。
这不由得给了我巨大的启发。具体而言,采用CSS的grid属性来绘制树状图有如下优势:
- 在网络资料的帮助下,我这半吊子的水平确实可以实现grid的排版;
- grid网格中元素的位置可以由一组坐标确定,方便计算机处理;
- 大部分用户的浏览器都支持使用grid进行排版。
从无序列表转化为树状图
元素在无序列表中的相对位置可以由一串数字说明(我们可以将其称为“树坐标”),比如某个元素无序列表中的第一个,也就是在树状图的根部,它的坐标可以记为:
(0)
表示它是同辈树枝中的长子,且没有父辈,它自己就是第一代。
假如有多个枝并列,比如:
*<!--长子-->
*<!--次子-->
*<!--幼子-->
它们的坐标可以分别记为:
(0)
(1)
(2)
假如这三个枝分别有两个子代,比如
*<!-- 伯 -->
**<!--长子-->
**<!--次子-->
*<!-- 仲 -->
**<!--长子-->
**<!--次子-->
*<!-- 叔 -->
**<!--长子-->
**<!--次子-->
它们的坐标就能分别记为
(0)
(0,0)
(0,1)(1)
(1,0)
(1,1)(2)
(2,0)
(2,1)
通过这样的树坐标,就能很清楚地看到树状图上每一项的继承关系和相对位置。而grid是栅格布局的,通过一个二维坐标便能确定树状图上所有分支和连接线的绝对位置。我们将前者叫做“树坐标”,后者叫做“表坐标”,那么有以下结论是不言而喻的:
存在某种映射关系,能将树坐标映射为表坐标。
我们只要找到这种映射关系,就找到了无序列表与树状图之间的桥梁,要将无序列表转化为树状图也就很容易了。
工作流程总结
最后我们将上文汇总成流程图,就能得到这样的结果:
如果不出意外的话,我们只要按这个流程逐步设计程序,就能实现我们的需求。但愿如此。