最近有一部关于程序员的电视剧,里边有一段关于期中考试要用程序画一个爱心的桥段。
电视剧里的代码,真的能运行吗?

今天就给大家分析下剧中出现的“爱心”代码,并且来复刻一下最后男主完成的酷炫跳动爱心。
剧中代码赏析

1路人同学的代码

虽然剧中说是“C 语言期中考试”,但这位同学的代码名叫 draw2.py,一个典型的 Python 文件,再结合截图中的 pen.forward、pen.setpos 等方法来看,应该是用 turtle 海龟作图库来画爱心。那效果通常是这样的:
import
 turtle 
as
 t

t.color(
'red'
)

t.setheading(
50
)

t.begin_fill()

t.circle(
-100
170
)

t.circle(
-300
40
)

t.right(
38
)

t.circle(
-300
40
)

t.circle(
-100
170
)

t.end_fill()

t.done()

而不是剧中那个命令行下用 1 组成的不规则的图形。

2课代表向路人同学展示的优秀代码

及效果:
这确实是C语言代码了,但文件依然是以 .py 为后缀,并且 include 前面没有加上 #,这显然是没法运行的。
里面的内容是可以画出爱心的,用的是这个爱心曲线公式:
然后遍历一个 15*17 的方阵,计算每个坐标是在曲线内还是曲线外,在内部就输出 # 或 *,外部就是 -。
用 python 改写一下是这样的:

for
 y 
in
 range(
9
-6
-1
):

for
 x 
in
 range(
-8
9
):

        print(
'*##*'
[(x+
10
)%
4
if
 (x*x+y*y
-25
)**
3
 < 
25
*x*x*y*y*y 
else'-'
, end=
' '
)

    print()

效果:
稍微改一下输出,还能做出前面那个全是 1 的效果:

for
 y 
in
 range(
9
-6
-1
):

for
 x 
in
 range(
-8
9
):

        print(
'1'if
 (x*x+y*y
-25
)**
3
 < 
25
*x*x*y*y*y 
else' '
, end=
' '
)

    print()

但跟剧中所谓的效果相去甚远。

3主角狂拽酷炫 D 炸天的跳动爱心:

代码有两个片段:
但这两个片段也不 C 语言,而是 C++,且两段并不是同一个程序,用的方法也完全不一样。
第一段代码跟前面一种思路差不多,只不过没有直接用一条曲线,而是上半部用两个圆形,下半部用两条直线,围出一个爱心。
改写成 Python 代码:

size = 
10
for
 x 
in
 range(size):

for
 y 
in
 range(
4
*size+
1
):

        dist1 = ((x-size)**
2
 + (y-size)**
2
) ** 
0.5
        dist2 = ((x-size)**
2
 + (y
-3
*size)**
2
) ** 
0.5
if
 dist1 < size + 
0.5or
 dist2 < size + 
0.5
:

            print(
'V'
, end=
' '
)

else
:

            print(
' '
, end=
' '
)

    print()


for
 x 
in
 range(
1
2
*size):

for
 y 
in
 range(x):

        print(
' '
, end=
' '
)

for
 y 
in
 range(
4
*size+
1-2
*x):

        print(
'V'
, end=
' '
)

    print()

运行效果:
第二段代码用的是基于极坐标的爱心曲线,是遍历角度来计算点的位置。公式是:
计算出不同角度对应的点坐标,然后把它们连起来,就是一个爱心。
from
 math 
import
 pi, sin, cos

import
 matplotlib.pyplot 
as
 plt

no_pieces = 
100
dt = 
2
*pi/no_pieces

t = 
0
vx = []

vy = []

while
 t <= 
2
*pi:

    vx.append(
16
*sin(t)**
3
)

    vy.append(
13
*cos(t)
-5
*cos(
2
*t)
-2
*cos(
3
*t)-cos(
4
*t))

    t += dt

plt.plot(vx, vy)

plt.show()

效果:
代码中循环时用到的 2π 是为了保证曲线长度足够绕一个圈,但其实长一点也无所谓,即使 π=100 也不影响显示效果,只是相当于同一条曲线画了很多遍。所以剧中代码里写下 35 位小数的 π,还被女主用纸笔一字不落地抄写下来,实在是让程序员无法理解的迷惑行为。
但不管写再多位的 π,上述两段代码都和最终那个跳动的效果差了五百只羊了个羊。
跳动爱心实现
下面就来挑战一下用 Python 实现最终的那个跳动爱心的效果。
1、想要绘制动态的效果,必定要借助一些库的帮助,不然代码量肯定会让你感动得想哭。这里我们将使用 pgzero 库。然后结合最后那个极坐标爱心曲线代码,先绘制出曲线上离散的点。
import
 pgzrun

from
 math 
import
 pi, sin, cos


no_p = 
100
dt = 
2
*
3
/no_p

t = 
0
x = []

y = []

while
 t <= 
2
*
3
:

    x.append(
16
*sin(t)**
3
)

    y.append(
13
*cos(t)
-5
*cos(
2
*t)
-2
*cos(
3
*t)-cos(
4
*t))

    t += dt


defdraw():
    screen.clear()

for
 i 
in
 range(len(x)):

        screen.draw.filled_rect(Rect((x[i]*
10
+
400
, -y[i]*
10
+
300
), (
4
4
)), 
'pink'
)


pgzrun.go()

2、把点的数量增加,同时沿着原点到每个点的径向加一个随机数,并且这个随机数是按照正态分布来的(半个正态分布),大概率分布在曲线上,向曲线内部递减。这样,就得到这样一个随机分布的爱心效果。
...

no_p = 
20000
...

while
 t <= 
2
*pi:

    l = 
10
 - abs(random.gauss(
10
2
) - 
10
)

    x.append(l*
16
*sin(t)**
3
)

    y.append(l*(
13
*cos(t)
-5
*cos(
2
*t)
-2
*cos(
3
*t)-cos(
4
*t)))

    t += dt

...

3、下面就是让点动起来,这步是关键,也有一点点复杂。为了方便对于每个点进行控制,这里将每个点自定义成了一个 Particle 类的实例。从原理上来说,就是给每个点加一个缩放系数,这个系数是根据时间变化的正弦函数,看起来就会像呼吸的节律一样。
classParticle():
def__init__(self, pos, size, f):
        self.pos = pos

        self.pos0 = pos

        self.size = size

        self.f = f


defdraw(self):
        screen.draw.filled_rect(Rect((
10
*self.f*self.pos[
0
] + 
400
-10
*self.f*self.pos[
1
] + 
300
), self.size), 
'hot pink'
)


defupdate(self, t):
        df = 
1
 + (
2
 - 
1.5
) * sin(t * 
3
) / 
8
        self.pos = self.pos0[
0
] * df, self.pos0[
1
] * df


...


t = 
0
defdraw():
    screen.clear()

for
 p 
in
 particles:

        p.draw()


defupdate(dt):
global
 t

    t += dt

for
 p 
in
 particles:

        p.update(t)

4、 剧中爱心跳动时,靠中间的点波动的幅度更大,有一种扩张的效果。所以再根据每个点距离原点的远近,再加上一个系数,离得越近,系数越大。
classParticle():
    ...

defupdate(self, t):
        df = 
1
 + (
2
 - 
1.5
 * self.f) * sin(t * 
3
) / 
8
        self.pos = self.pos0[
0
] * df, self.pos0[
1
] * df

5、最后再用同样的方法画一个更大一点的爱心,这个爱心不需要跳动,只要每一帧随机绘制就可以了。
defdraw():
    ...

    t = 
0
while
 t < 
2
*pi:

        f = random.gauss(
1.1
0.1
)

        x = 
16
*sin(t)**
3
        y = 
13
*cos(t)
-5
*cos(
2
*t)
-2
*cos(
3
*t)-cos(
4
*t)

        size = (random.uniform(
0.5
,
2.5
), random.uniform(
0.5
,
2.5
))

        screen.draw.filled_rect(Rect((
10
*f*x + 
400
-10
*f*y + 
300
), size), 
'hot pink'
)

        t += dt * 
3
合在一起,搞定!
总结一下,就是在原本的基础爱心曲线上加上一个正态分布的随机量、一个随时间变化的正弦函数和一个跟距离成反比的系数,外面再套一层更大的随机爱心,就得到类似剧中的跳动爱心效果。
当然,咱缺的不是代码,是女主角。
·················END·················

推荐阅读

继续阅读
阅读原文