大家好,欢迎来到 Crossin的编程教室 !
前几天,后台老有小伙伴留言“爱心代码”。这不是Crossin很早之前发过的内容嘛,怎么最近突然又被人翻出来了?后来才知道,原来是一部有关程序员的青春偶像剧《点燃我,温暖你》在热播,而剧中有一段关于期中考试要用程序画一个爱心的桥段。
于是出于好奇,Crossin就去看了这一集(第5集,不用谢)。这一看不要紧,差点把刚吃的鸡腿给喷出来--槽点实在太多了!
忍不住做了个欢乐吐槽向的代码解读视频,在某平台上被顶到了20个w的浏览,也算蹭了一波人家电视剧的热度吧……
看剧学编程:你用程序画爱心是认真的吗?
下面是图文版,给大家分析下剧中出现的“爱心”代码,并且来复刻一下最后男主完成的酷炫跳动爱心。
剧中代码赏析
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.5
or 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位小数的π,还被女主用纸笔一字不落地抄写下来,实在是让程序员无法理解的迷惑行为。
但不管写再多位的π,上述两段代码都和最终那个跳动的效果差了五百只羊了个羊。
跳动爱心实现
作为一个总是在写一些没什么乱用的代码的编程博主,Crossin当然也不会放过这个机会,下面就来挑战一下用 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
-
-
def
draw():
-
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类的实例。
从原理上来说,就是给每个点加一个缩放系数,这个系数是根据时间变化的正弦函数,看起来就会像呼吸的节律一样。
-
class
Particle():
-
def
__init__(
self, pos, size, f):
-
self.pos = pos
-
self.pos0 = pos
-
self.size = size
-
self.f = f
-
-
def
draw(
self):
-
screen.draw.filled_rect(Rect((
10*self.f*self.pos[
0] +
400, -
10*self.f*self.pos[
1] +
300), self.size),
'hot pink')
-
-
def
update(
self, t):
-
df =
1 + (
2 -
1.5) * sin(t *
3) /
8
-
self.pos = self.pos0[
0] * df, self.pos0[
1] * df
-
-
...
-
-
t =
0
-
def
draw():
-
screen.clear()
-
for p
in particles:
-
p.draw()
-
-
def
update(
dt):
-
global t
-
t += dt
-
for p
in particles:
-
p.update(t)
4. 剧中爱心跳动时,靠中间的点波动的幅度更大,有一种扩张的效果。所以再根据每个点距离原点的远近,再加上一个系数,离得越近,系数越大。
-
class
Particle():
-
...
-
def
update(
self, t):
-
df =
1 + (
2 -
1.5 * self.f) * sin(t *
3) /
8
-
self.pos = self.pos0[
0] * df, self.pos0[
1] * df
5. 最后再用同样的方法画一个更大一点的爱心,这个爱心不需要跳动,只要每一帧随机绘制就可以了。
-
def
draw():
-
...
-
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
合在一起,搞定!
总结一下,就是在原本的基础爱心曲线上加上一个正态分布的随机量、一个随时间变化的正弦函数和一个跟距离成反比的系数,外面再套一层更大的随机爱心,就得到类似剧中的跳动爱心效果。
但话说回来,真有人会在考场上这么干吗?
除非真的是超级大学霸,不然就是食堂伙食太好--
吃太饱撑的……
代码已开源:python666.cn/c/9
如二创发布请注明代码来源:Crossin的编程教室
转载:https://blog.csdn.net/qq_40523737/article/details/127967411