20241105 2024-2025-2 《Python程序设计》实验四报告

课程:《Python程序设计》
班级:2411
姓名: 王梓墨
学号:20241105
实验教师:王志强
实验日期:2025年6月1日
必修/选修: 公选课
一.实验内容
马上要纪念日了,给她设计一个爱心
(但不知道她的电脑能不能跑出来)
二.实验过程
最开始没什么思路,求助于deepseek,得知可以用数学函数绘制出爱心,并在此基础上加诸多特效。
为了做出此代码,利用一整个假期翻阅了许多参考书并上网搜索了许多资料以及相关B站视频。
1.导入画布
`import random
from math import sin, cos, pi, log
from tkinter import *

CANVAS_WIDTH = 640
CANVAS_HEIGHT = 480
CANVAS_CENTER_X/Y = 宽高/2 # 画布中心坐标
IMAGE_ENLARGE = 11 # 爱心缩放倍数
HEART_COLOR = "#FFC0CB" # 爱心颜色(粉色)`

2.爱心形状数学函数
首先使用参数方程  x=16sin³t,y=13cost-5cos2t-2cos3t-cos4t  生成爱心轮廓点,并通过缩放和平移(加中心坐标)将数学坐标映射到画布位置
def heart_function(t, shrink_ratio=IMAGE_ENLARGE): x = 16 * (sin(t)**3) # 爱心参数方程x坐标 y = -(13*cos(t) - 5*cos(2*t) - 2*cos(3*t) - cos(4*t)) # y坐标(负号翻转方向) x,y = 坐标 * 缩放比例 + 中心坐标 # 转换为画布坐标 return 整数坐标

这一步在编译时候多次出错,爱心的位置要么偏移,要么超出画布,后来发现heart_function中y坐标未正确翻转(原数学方程y轴方向与画布方向Y轴方向相反)

3.扩散效果函数
`def scatter_inside(x,y,beta=0.15):
# 基于对数随机数生成扩散偏移量
ratio_x = -beta * log(random.random())
ratio_y = -beta * log(random.random())
dx = ratio_x(x-中心X) # 偏移量与到中心的距离成正比
dy = ratio_y
(y-中心Y)
return x-dx, y-dy # 向中心反方向扩散

def shrink(x,y,ratio):
# 收缩效果:距离中心越近,收缩力越强
force = -1 / ((距离中心距离)^0.6)
dx/dy = ratioforce(偏移量)
return x-dx, y-dy`

运用了scatter_inside函数,生成边缘扩散点
shrink:收缩
4.曲线函数
控制动画节奏,缩放比例
def curve(p): return 2*(2*sin(4*p))/(2*pi) # 生成周期性波动的曲线值(-2/pi到2/pi之间)

此步颇为不易,放出来动画经常闪烁令人眼花缭乱,不得不修改好几次curve函数的参数设置
5.核心逻辑
`class Heart:
def init(self, generate_frame=20):
self._points = 爱心轮廓点集合
self._edge_diffusion_points = 边缘扩散点集合
self._center_diffusion_points = 中心扩散点集合
self.all_points = 各帧动态点坐标字典
self.build(2000) # 初始化生成2000个轮廓点
self.calc(generate_frame) # 计算各帧动画数据

def build(self, number):
    # 生成爱心轮廓点
    for _ in range(number):
        t=随机角度,添加(heart_function(t))到_points
    # 生成边缘扩散点:每个轮廓点扩散3次
    for 点 in _points:
        for _ in range(3):
            添加scatter_inside(点, 0.05)到_edge_diffusion_points
    # 生成中心扩散点:随机选轮廓点扩散4000次
    for _ in range(4000):
        点=随机选轮廓点,添加scatter_inside(点, 0.17)到_center_diffusion_points

def calc_position(self, x,y, ratio):
    # 计算动态坐标:结合吸引力和随机偏移
    force = 1/(距离中心距离^0.52)
    dx/dy = ratio*force*偏移量 + 随机±1
    return x-dx, y-dy

def calc(self, generate_frame):
    # 计算每一帧的点坐标
    ratio = 10*curve(帧号/10*pi)  # 缩放比例随曲线变化
    halo_radius = 光晕半径(基于曲线值)
    halo_number = 光晕点数(基于曲线平方)
    # 生成光晕点(外围随机点)
    heart_halo_point = set()
    for _ in range(halo_number):
        生成缩放后的爱心点,随机偏移,去重后添加到all_points
    # 处理轮廓点、边缘扩散点、中心扩散点的动态坐标
    for 点集合 in [_points, _edge_diffusion_points, _center_diffusion_points]:
        计算每个点的新坐标,添加到all_points

def render(self, canvas, frame):
    # 在画布上绘制当前帧的所有点(矩形模拟像素点)
    for x,y,size in self.all_points[frame%generate_frame]:
        canvas.create_rectangle(x,y,x+size,y+size, fill=颜色)`

初始化:生成爱心轮廓点和扩散点
构建阶段(build):创建基础点集
计算阶段(calc):根据动画帧号计算每个点的动态位置
渲染阶段(render):在画布上绘制所有点
6.动画驱动函数
使用Tkinter的 after 方法实现定时刷新,形成动画效果
def draw(main, canvas, heart, frame=0): canvas.delete('all') # 清空画布 heart.render(canvas, frame) # 绘制当前帧 main.after(160, draw, ..., frame+1) # 160ms后绘制下一帧(约6帧/秒)
三.实验代码
`import random
from math import sin, cos, pi, log
from tkinter import *

CANVAS_WIDTH = 640
CANVAS_HEIGHT = 480
CANVAS_CENTER_X = CANVAS_WIDTH / 2
CANVAS_CENTER_Y = CANVAS_HEIGHT / 2
IMAGE_ENLARGE = 11
HEART_COLOR = "#FFC0CB" #ff2121

def heart_function(t, shrink_ratio: float = IMAGE_ENLARGE):

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

x *= shrink_ratio
y *= shrink_ratio

x += CANVAS_CENTER_X
y += CANVAS_CENTER_Y

return int(x), int(y)

def scatter_inside(x, y, beta=0.15):

ratio_x = - beta * log(random.random())
ratio_y = - beta * log(random.random())

dx = ratio_x * (x - CANVAS_CENTER_X)
dy = ratio_y * (y - CANVAS_CENTER_Y)

return x - dx, y - dy

def shrink(x, y, ratio):

force = -1 / (((x - CANVAS_CENTER_X) ** 2 + (y - CANVAS_CENTER_Y) ** 2) ** 0.6)  # 这个参数...
dx = ratio * force * (x - CANVAS_CENTER_X)
dy = ratio * force * (y - CANVAS_CENTER_Y)
return x - dx, y - dy

def curve(p):

return 2 * (2 * sin(4 * p)) / (2 * pi)

class Heart:

def __init__(self, generate_frame=20):
    self._points = set()  # 原始爱心坐标集合
    self._edge_diffusion_points = set()  # 边缘扩散效果点坐标集合
    self._center_diffusion_points = set()  # 中心扩散效果点坐标集合
    self.all_points = {}  # 每帧动态点坐标
    self.build(2000)

    self.random_halo = 1000

    self.generate_frame = generate_frame
    for frame in range(generate_frame):
        self.calc(frame)

def build(self, number):

    for _ in range(number):
        t = random.uniform(0, 2 * pi)
        x, y = heart_function(t)
        self._points.add((x, y))


    for _x, _y in list(self._points):
        for _ in range(3):
            x, y = scatter_inside(_x, _y, 0.05)
            self._edge_diffusion_points.add((x, y))


    point_list = list(self._points)
    for _ in range(4000):
        x, y = random.choice(point_list)
        x, y = scatter_inside(x, y, 0.17)
        self._center_diffusion_points.add((x, y))

@staticmethod
def calc_position(x, y, ratio):

    force = 1 / (((x - CANVAS_CENTER_X) ** 2 + (y - CANVAS_CENTER_Y) ** 2) ** 0.520)  # 魔法参数

    dx = ratio * force * (x - CANVAS_CENTER_X) + random.randint(-1, 1)
    dy = ratio * force * (y - CANVAS_CENTER_Y) + random.randint(-1, 1)

    return x - dx, y - dy

def calc(self, generate_frame):
    ratio = 10 * curve(generate_frame / 10 * pi)  # 圆滑的周期的缩放比例

    halo_radius = int(4 + 6 * (1 + curve(generate_frame / 10 * pi)))
    halo_number = int(3000 + 4000 * abs(curve(generate_frame / 10 * pi) ** 2))

    all_points = []

    heart_halo_point = set()
    for _ in range(halo_number):
        t = random.uniform(0, 2 * pi)
        x, y = heart_function(t, shrink_ratio=11.6)
        x, y = shrink(x, y, halo_radius)
        if (x, y) not in heart_halo_point:
            heart_halo_point.add((x, y))
            x += random.randint(-14, 14)
            y += random.randint(-14, 14)
            size = random.choice((1, 2, 2))
            all_points.append((x, y, size))

    for x, y in self._points:
        x, y = self.calc_position(x, y, ratio)
        size = random.randint(1, 3)
        all_points.append((x, y, size))

    for x, y in self._edge_diffusion_points:
        x, y = self.calc_position(x, y, ratio)
        size = random.randint(1, 2)
        all_points.append((x, y, size))

    for x, y in self._center_diffusion_points:
        x, y = self.calc_position(x, y, ratio)
        size = random.randint(1, 2)
        all_points.append((x, y, size))

    self.all_points[generate_frame] = all_points

def render(self, render_canvas, render_frame):
    for x, y, size in self.all_points[render_frame % self.generate_frame]:
        render_canvas.create_rectangle(x, y, x + size, y + size, width=0, fill=HEART_COLOR)

def draw(main: Tk, render_canvas: Canvas, render_heart: Heart, render_frame=0):
render_canvas.delete('all')
render_heart.render(render_canvas, render_frame)
main.after(160, draw, main, render_canvas, render_heart, render_frame + 1)

if name == 'main':
root = Tk() # 一个Tk
canvas = Canvas(root, bg='black', height=CANVAS_HEIGHT, width=CANVAS_WIDTH)
canvas.pack()
heart = Heart()
draw(root, canvas, heart)
root.mainloop()`
四.实验结果
先放个静态的图片在这里

实际上做出来的效果是动态的
五.感悟
“人生苦短,我学Python”
上学期我C语言学得是一点也不好,但这学期的Python课却是另一种思路,带我走进了另一个编码的世界。从实验一到实验二再到实验三实验四,选课之前我不会想到会写完这么多代码,当然也不会想到会花这么多时间去写代码,去和同学们一起研究代码。这实实在在地提升了一些我的代码水平,给了我去面对输惧结垢的勇气。以后我研究研究,争取能通过Python作出一个自己的小游戏来玩玩。最后,Python,nb,不后悔自己选了python这门课。
六.感谢
最开始抢课时候单纯是听学长学姐推荐Python这门课,所以就想着抢一下学学看,没想到能遇到强哥这么一个好老师。在这所学校里,能认出我穿的是尤文球衣的同学有很多,但老师倒是头一个(本来合计会没有了呢哈哈哈),这也让我喜欢上了强哥和这门课。后来关注了强哥小红书才感受到,强哥真的是吾辈楷模,能一直工作到深夜才离开,强哥是我们所有人的榜样。通过这节课我不仅学到了如何写代码,还让我找回了丢失已久的专注力。真的很感谢强哥,如果以后有机会我还去选强哥的课。最后感谢一下当时手快抢上Python课的我自己(doge)
“那些看似波澜不惊的日复一日,终将在某一日,让我们看到坚持的意义。”
话不多说,上图


希望以后有机会和强哥切磋切磋球技
七.参考资料

posted @ 2025-06-08 11:35  王梓墨  阅读(39)  评论(0)    收藏  举报