颜林林的个人网站

Linlin Yan's Personal Website

[不靠谱颜论] 用ggplot2画迷宫

2020-07-18 22:31

导言: 对ggplot2来说,画几个迷宫,还不就跟玩一样。


ggplot2绝对算得上我掌握的最实用编程技能。随机地画一大堆迷宫,每个迷宫都有且只有一条正确路径,这对于讨好喜欢走迷宫的小闺女来说,可真是太好用了。

下面就来介绍,这个画迷宫的想法,是如何变成现实的。

1

首先,对题图这样的迷宫,可以将其考虑是一个w x h的网格(wh分别表示横向和纵向分别有多少个格子),从中拆除部分相邻格子间的隔板后,而得到的结果。

为方便ggplot2使用,在构造数据时,直接构造w x h行数据记录,并且用xy两个字段来分别表示每条记录对应的坐标位置,其他字段则分别表示该坐标的其他属性。于是就有如下代码,来完成一个“拆除隔板”前的迷宫的绘制:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
library(tidyverse)

w <- 20
h <- 12
a <- expand.grid(x = 0:w, y = 0:h) %>%
  mutate(hline = (x < w), vline = (y < h))

a %>%
  ggplot(aes(x, y)) +
  geom_segment(aes(xend = x + hline, yend = y)) +
  geom_segment(aes(xend = x, yend = y + vline)) +
  theme_void()

这里,使用了一个实现技巧,即考虑每个格子只拥有右侧和上侧的隔板(对应下面代码中的vlinehline,这两个字段若取值为FALSE,则表示拆除了该隔板,即不再绘制),左侧和下侧的隔板则共享其相邻格子的。相应地,其实我们构造了一个(w + 1) x (h + 1)行的数据表。为了去除网格在上边界和有边界的多余线段,在构造hlinevline初始值时,限制了相应的范围。

有了上面这个框架,接下去,我们只需要设置hlinevline字段,实现合适的隔板拆除,迷宫就可以完成。

2

该拆除哪些隔板呢?

这需要一个循环过程。首先假定有一个精灵,站在迷宫的起点,然后采取随机游走的方式,走到相邻的下一个格子。游走的过程需要进行标记,避免走入已经到达过的格子。依靠这个游走的过程进行隔板拆除,最终,就能确保有且只有一条正确路径,到达预期终点。 下面这个动画,比较直观地展示了上述过程。相应的代码,就不在这里详细解释了。

值得注意的是,这个精灵并不是完全一条道走到黑。如果是,则生成的迷宫很可能会变得很简单。所以,我们需要按照某个概率(比如10%),从已经到达过的某个格子出发,分叉出去,走另一条路径。这个概率,可以用来帮助调整迷宫的复杂度。

3

在上述动画中,展示了每次步进,拆除一片隔板,从而生成整个迷宫的过程。这个动画的每一帧,也同样都是基于ggplot2语句进行绘制的。其间无非是增加了几个字段,分别表示出对应路径及其他图像元素,然后将其映射到对应的geom_xxx()语句即可。 此外,ggplot2有个映射坐标系的强大功能,可以直接将笛卡尔坐标系(即我们通常熟悉的互相垂直的x轴和y轴所构成的平面)转化为极坐标系。这让我们可以直接把上面实现的迷宫,变成一个圆形迷宫。

最终迷宫实现(包括上述演示动画)的完整代码,均可在github上找到,点击“阅读原文”可进行查看。

--- END ---

注:本文发表于“不靠谱颜论”公众号,并同步至本站。