转座集思广益
有时我陷入困境,不得不寻找从不同角度思考任务的方法。 有些任务可以矩阵或表格的形式显示。 它们的结构如下所示:
我正在使用的单元格按行和列排列。 让我们以一个简单的游戏为例:
线是角色类别:战士,法师,小偷。
列是操作的类型:攻击,防御,特殊操作。
矩阵包含用于处理每种字符类型的每种动作类型的所有代码。
代码是什么样的? 通常,此类结构被组织为以下模块:
Fighter
将包含用于处理剑击,减少装甲和特殊强力打击伤害的代码。Mage
将包含火球处理代码,伤害反射和特殊的冰冻攻击。Thief
将包含处理匕首攻击,避免躲闪伤害和特殊缴械攻击的代码。
有时转置矩阵很有用。 我们可以把它放在另一个轴上
:
Attack
将包含用于处理挥杆,火球和匕首攻击的代码。Defend
将包含处理损害减少,反映损害并避免损害的代码。Special
将包含强大的打击,冻结和撤防处理代码。
有人告诉我,一种风格是“好”,另一种风格是“不好”。 但是对我来说,为什么一切都应该是那样并不明显。 原因是
假设我们经常添加新的字符类别(名词),而很少添加新的动作类型(动词)。 这样,我可以使用新模块添加代码,而无需接触
所有可用的代码。 在您的游戏中,一切都可能不同。 查看转置矩阵,我知道该假设的存在,并且可以对其怀疑。 然后,我将考虑所需的灵活性类型,然后才选择代码结构。
让我们看另一个例子。
编程语言的解释具有与原语相对应的各种类型的节点:常量,运算符,循环,分支,函数,类型等。 我们需要为它们全部生成代码。
太好了! 您可以为每种类型的节点创建一个类,并且它们都可以从基类
Node
继承。 但是我们依赖于这样的假设:我们将添加行,而较少地添加列。 优化编译器中会发生什么? 我们正在添加新的优化通道。 并且每个都是新的专栏。
如果我想添加一个新的优化过程,那么我将需要为每个类添加一个新方法,并且所有优化过程的代码都将分配到不同的模块中。 我想避免这种情况! 因此,在某些系统中,在此之上添加了另一层。 使用访问者模式,我可以将所有用于合并循环的代码存储在一个模块中,而不是将其分解为多个文件。
如果您查看转置矩阵,我们将打开另一种方法:
现在,我可以使用带
标记的联合和
模式匹配来代替带有
方法的
类 (并非所有编程语言都支持它们)。 因此,每个优化遍历的整个代码都将存储在一起,并且可以在没有访问者模式间接影响的情况下进行。
从矩阵的角度看问题通常很有用。 如果将其应用于每个人都在思考的面向对象的结构,它可能会导致我想到其他东西,例如“实体组件系统”模式,关系数据库或反应式编程。
这不仅适用于代码。 这是将这种想法应用于产品的示例。 假设有人有不同的兴趣:
如果我正在开发一个社交网站,则可以允许人们关注其他
人的新闻。 尼克可以注册爱丽丝,因为他们俩都对汽车感兴趣,而在芬雅,则因为他们都对旅行感兴趣。 但尼克还将获得爱丽丝的数学职位和芬雅的政治职位。 如果我正在考虑转置矩阵,那么我可以允许人们订阅
主题 。 尼克可以加入一群汽车爱好者以及一群旅行者。 Facebook和Reddit大约是在同一时间开始的,但是它们彼此互换。 Facebook允许您关注人们; Reddit允许您订阅主题。
当我陷入停顿或想考虑替代方案时,我会研究问题并在其中寻找不同的订购轴。 有时从不同的角度看问题可以提供更好的解决方案。
分解头脑风暴
我使用另一种称为分解的技术。
在代数中,
分解运算将形式为5x²+ 8x-21的多项式转换为(x + 3)·(5x-7)。 要求解方程5x²+ 8x-21 = 0,我们可以首先将其分解为(x + 3)·(5x-7)=0。然后我们可以说x + 3 = 0
或 5x-7 =0。展开可以将一项艰巨的任务变成一些更简单的任务。
让我们看一个例子:我有六个类:
File
,
EncryptedFile
,
GzipFile
,
EncryptedGzipFile
,
BzipFile
,
EncryptedBzipFile
。 我可以将它们分解成一个矩阵:
使用“装饰程序”模式(或杂质),我将六种不同的文件类型转换为四个组件:普通,gzip,bzip,加密。 这似乎并没有节省太多,但是如果我添加更多变体,节省的费用将会增加。 分解将O(M * N)分量转换为O(M + N)分量。
另一个例子:有时人们问我
“如何在C#中编写线性插值?”之类的问题。 。 我可以写很多潜在的教程:
如果有M个主题和N种语言,那么我可以编写M * N个教程。 但是,这是
很多工作。 相反,我将编写关于
插值的教程,其他人将编写有关C#的教程,然后读者将C#的知识与插值的知识结合起来,并在C#中编写其插值版本。
像换位一样,分解并不总是有用,但如果适用,它可能会非常有用。
向后头脑风暴
在前两部分中,我讨论了有时我如何处理任务,试图将其排列成矩阵。 有时这无济于事,然后我尝试从相反的方向看待任务。 例如,让我们看一下过程图的生成。 通常,我从噪声函数开始,然后添加八度音阶,调整参数并添加层。 我这样做是因为我需要具有某些属性的卡。
从参数实验开始是很有可能的,但是参数空间很大,而且我是否会找到最适合自己需求的参数尚不清楚。 因此,经过一些实验,我停下来并开始以相反的顺序进行思考:如果我可以描述我的需求,那么这将有助于找到参数。
正是这种动机使我学习代数。 如果我们有一个
5x²+ 8x-21 = 0形式的方程,那么
x是多少? 当我不知道代数时,我将求解此方程,尝试替换
x的不同值,首先随机选择它们,然后在感觉到已经接近解时调整它们。 代数为我们提供了一个朝着不同方向发展的工具。 她没有猜测答案,而是给了我一种设备(分解或二次方程式,或牛顿迭代搜索根的方法),我可以更自觉地使用它来搜索
x值(-3或7/5)。
我觉得我经常会遇到这种编程情况。 在进行过程图的生成时,在对参数进行了一段时间的试验之后,我停下来编译了
一个项目的游戏世界清单:
- 玩家必须在远离海岸的地方开始游戏。
- 升级时,玩家必须爬上坡。
- 玩家不应能够到达地图的边缘。
- 随着级别的提高,玩家必须加入小组。
- 海岸上应该有简单的怪物,没有太多变化。
- 在平原上,应该有各种各样中等难度的怪物。
- 在山区必须有复杂的boss怪物。
- 必须有某种界标可以让玩家保持在相同的难度水平,而另一种界标则可以让您在难度上上升或下降。
该列表的汇总导致创建以下限制:
- 游戏世界应该是具有许多海岸和中心小高峰的岛屿。
- 高度应对应于怪物的复杂性。
- 在低海拔和高海拔,生物群落的变异性应比中等高度低。
- 道路应保持相同的难度。
- 河流应从高处流向小处,并为玩家提供上下移动的能力。
这些限制导致我设计了一个地图生成器。 和我平时所做的一样,他带来了比我通过调整参数得到的卡更好的一组卡。
最终的文章引起了很多人的兴趣,他们希望基于Voronoi图创建地图。
单元测试是另一个示例。 建议我提出一个示例列表进行测试。 例如,对于六边形网格,我可能认为我需要检查条件
add(Hex(1, 2), Hex(3, 4)) == Hex(4, 6)
。 然后我可以记得您需要检查零:
add(Hex(0, 1), Hex(7, 9)) == Hex(7, 10)
。 然后我可以记得您需要检查负值:
add(Hex(-3, 4) + Hex(7, -8)) == Hex(4, -4)
。 好吧,太好了,我有一些单元测试。
但是,如果您再想一想,那么
实际上我会检查
add(Hex(A, B), Hex(C, D)) == Hex(A+C, B+D)
。 我根据此一般规则提出了上面显示的三个示例。 为了进行单元测试,我朝着与该规则相反的方向前进。 如果我可以将此规则直接编码到测试系统中,则系统本身将能够以相反的顺序工作以创建用于测试的示例。 这称为基于属性的测试。 (另请参见:
变形测试 )
另一个例子:约束的求解器。 在这样的系统中,用户描述了他想在输出中看到的内容,并且系统找到了满足这些约束的方法。 引用《程序内容生成书》
第8章 :
使用第3章中的构造方法以及第4章中的分形和噪声方法,我们可以通过调整算法直到对它们的输出数据满意为止来创建各种类型的输出数据。 但是,如果我们知道生成的内容应具有的属性,则直接指示我们希望通用算法查找符合我们标准的内容会更方便。
本书描述了答案集(ASP)的编程,其中描述了我们正在使用的答案的结构(瓷砖是地板和墙壁,瓷砖彼此邻接),我们正在寻找的解决方案的结构(地牢是一个小组)连接瓷砖的起点和终点)和解决方案的属性(侧面通道应包含不超过5个房间,迷宫中应有1-2个循环,在到达上司之前必须击败三个助手)。 之后,系统会创建可能的解决方案,并允许您决定如何处理它们。
最近,开发了一种约束求解器,由于其很酷的名称和好奇的演示:Wave Function Collapse,引起了极大的兴趣。
[在Habr上有一篇关于该求解器的文章 。]如果您给他示例图像以指示对相邻图块施加了哪些限制,他将创建与给定模式相对应的新示例。
WaveFunctionCollapse是野外约束解决方案中描述了他的工作:
WFC无需回头即可实现贪婪的搜索方法。 本文探讨了WFC作为基于约束的决策方法的示例。
在约束求解器的帮助下,我已经取得了很多成就。 与代数一样,在学习如何有效使用它们之前,我需要学习很多东西。
另一个例子:
我创造的
宇宙飞船 。 播放器可以将引擎拖动到任何地方,并且当您单击W,A,S,D,Q,E时,系统将确定需要激活哪些引擎。例如,在此飞船中:
如果要向前飞行,则包括两个后置发动机。 如果要左转,请打开右后排发动机和左前排发动机。 我试图寻找一种解决方案,迫使系统
迭代许多参数 :
该系统有效,但并不完美。 后来我意识到这是另一个示例,它可以提供相反方向的解决方案。 事实证明,航天器的运动可以用
线性限制系统来描述。 如果我理解了这一点,我可以使用可以准确解决约束的现成库,而不是使用我的反复试验方法来返回近似值。
另一个示例:G9.js项目,您可以在其中拖动屏幕上某个功能的
输出 ,并确定如何更改
输入数据以匹配所需的输出数据。
G9.js演示看起来很棒! 确保在Rings演示中取消对“取消注释以下一行”行的注释。
有时以相反的顺序思考任务很有用。 经常发现,这比直接推理为我提供了
更好的解决方案。