本项目效果初始截图如下
动画见本人b站投稿:https://www.bilibili.com/video/av95491382
本项目对应github地址:https://github.com/BigShuang
python版本:3.6,pygame版本:1.9.3。(python版本一致应该就没什么问题)
样例gif如下
======================= 大爽歌作,made by big shuang =======================
一、思路分析
冒泡排序的思路感觉没有什么需要多做赘述的了。
所以这里主要讲下可视化的问题
这里为了方便,采用面向对象的思路编写代码。
其实主要需要三个类就要。
1 - 要实现一个内部有数字的圆点,同时这个圆点大小和数字相关联。
2 - 在实现一个管理这些圆点列表的,可以认为和是和数组相对应的列表管理者。
3 - 实现可视化
二、代码实现
1 在开头,先导入需要的类再设置好相关的常量
import pygame
import random
FPS = 60 # 游戏帧率
WIN_WIDTH = 1200 # 窗口宽度
WIN_HEIGHT = 980 # 窗口高度
BUBBLE_SPACE = 40 # 气泡之间的间距
INIT_R = 10 # 气泡初始大小-半径
DR = 4 # 气泡数字值一个单位对应的气泡半径增加量
NUMBER = 10 # 气泡个数
COLORS = {
"bg": (240, 255, 255), # 背景颜色
"bubble": (135, 206, 235), # 气泡颜色
"select": (0, 139, 139), # 被选择的气泡颜色
"text": (0, 0, 0), # 文本颜色
}
pygame.init() # pygame 初始化,必须有,且必须在开头
try:
# 如果你电脑自带有Arial字体,这里可以直接运行,(一般电脑都会有的)
font = pygame.font.SysFont("Arial", 20)
except Exception:
# 如果你本地没有Arial字体,你需要在项目里面建立一个font文件夹存放simkai.ttf字体文件,
# 字体文件可在我的github下载
font = pygame.font.Font("./font/simkai.ttf", 20)
2 建立Bubble类实现气泡
建立Bubble类,实现气泡,即一个内部有数字的圆点
class Bubble:
speed = 2 # 速度
color = COLORS["bubble"]
def __init__(self, master, x, y, v, r):
self._master = master # 父控件
self.cx = x # 气泡中心横座标 - x
self.cy = y # 气泡中心纵座标 - y
self.v = v # 气泡内部数字
self.radius = r # 气泡半径
# 注释说明1-1
# 由于pygame自己绘制的圆锯齿太大,看起来十分不光滑
# 想要光滑的圆,只能从png文件中导入圆的图片(图片可前往我的github下载)
# 这一部分对於单纯的pygame动画学习者而言,并不是非常重要,所以我把与此相关的代码都给注释掉了
# 有需要的把所有标明注释说明1-1的代码解注释就好
# self.image = pygame.image.load("img/bubble.png")
# self.select_image = pygame.image.load("img/select.png")
# self.resize()
def up(self):
# 气泡上浮
self.cy -= DR
def down(self):
# 气泡下沉
self.cy += DR
# 绘制气泡
def draw(self, current=False):
text = font.render(str(self.v), 1, COLORS["text"])
if current:
pygame.draw.circle(self._master, COLORS["select"], (self.cx, self.cy), self.radius)
else:
pygame.draw.circle(self._master, self.color, (self.cx, self.cy), self.radius)
text_width = text.get_width()
text_height = text.get_height()
self._master.blit(text, (self.cx - text_width // 2, self.cy - text_height // 2))
# 见注释说明1-1
# def resize(self):
# self.image = pygame.transform.scale(self.image, (self.radius * 2, self.radius * 2))
# self.select_image = pygame.transform.scale(self.select_image, (self.radius * 2, self.radius * 2))
# def draw_pic(self, current=False):
# if current:
# self._master.blit(self.select_image, (self.cx - self.radius, self.cy - self.radius))
# else:
# self._master.blit(self.image, (self.cx - self.radius, self.cy - self.radius))
#
# text = font.render(str(self.v), 1, COLORS["text"])
# text_width = text.get_width()
# text_height = text.get_height()
# self._master.blit(text, (self.cx - text_width // 2, self.cy - text_height // 2))
3 建立BubbleManager类管理气泡
建立BubbleManager类管理气泡,并进行冒泡排序控制
class BubbleManager:
def __init__(self, master, x, height, arr=[]):
self._master = master # 父控件窗体
self.bubblelist = [] # bubble列表
# 数组
if arr:
self.arr = arr
else:
self.arr = [i for i in range(1, NUMBER)]
random.shuffle(self.arr)
# 根据数组中的数字建立Bubble对象,添加到bubblelist中
# 注意动画界面是从下往上展示数组的元素的
top_h = 0 # 上一个气泡最高点到底部的距离(即圆心到底的距离加上气泡半径),初始为0
for i in range(NUMBER-1):
v = self.arr[i]
vr = INIT_R + v * DR # 数字对应的半径大小
center_h = top_h + BUBBLE_SPACE + vr # 当前气泡圆心到界面底部的距离
top_h = center_h + vr # 记录当前气泡最高点到底部的距离,供下一个使用
bubble_i = Bubble(master, x, WIN_HEIGHT - center_h, v, vr)
self.bubblelist.append(bubble_i)
# 用于记录冒泡排序过程的值
self.i = 0
self.j = 0
# 记录当前气泡j是否正在上浮
self.bubbling = False
# 控制动画播放速度
self.count = 0
def draw(self):
# 绘制所有气泡
for j in range(len(self.bubblelist)):
db = self.bubblelist[j]
db.draw(j==self.j)
def bubble_done(self):
# 检查冒泡排序是否完成
return self.i >= NUMBER - 1
def bubble_j(self):
# 气泡j上浮动画
if not self.bubbling:
return
dbj0 = self.bubblelist[self.j]
dbj1 = self.bubblelist[self.j+1]
if dbj0.cy - dbj0.radius > dbj1.cy + dbj1.radius:
# j气泡在下
dbj0.up()
dbj1.down()
elif dbj0.cy > dbj1.cy:
# 气泡相交
cy0 = dbj0.cy
dbj0.cy = dbj1.cy - dbj1.radius + dbj0.radius
dbj1.cy = cy0 - dbj1.radius + dbj0.radius
elif (dbj1.cy - dbj1.radius) - (dbj0.cy + dbj0.radius) < BUBBLE_SPACE:
dbj0.up()
dbj1.down()
else:
self.bubblelist[self.j] = dbj1
self.bubblelist[self.j+1] = dbj0
self.bubbling = False
self.j += 1
return
def bubble_once(self):
# 进行一次冒泡排序
if self.j >= NUMBER - self.i - 2:
self.i += 1
self.j = 0
else:
if self.arr[self.j] > self.arr[self.j + 1]:
if self.count > 5:
self.count = 0
else:
self.count += 1
return
self.bubbling = True
self.arr[self.j], self.arr[self.j + 1] = self.arr[self.j + 1], self.arr[self.j]
self.bubble_j()
else:
if self.count > 10:
self.count = 0
self.j += 1
else:
self.count += 1
# 见注释说明1-1
# def draw_pic(self):
# for j in range(len(self.bubblelist)):
# db = self.bubblelist[j]
# db.draw_pic(j==self.j)
4 实现可视化界面
这里用一个Gui类去实现可视化
界面显示后,点击任意键盘按键即开始动画
class Gui:
def __init__(self, width, height, count, fps=FPS, arr=[]):
self.win = pygame.display.set_mode((width, height))
self.clock = pygame.time.Clock()
self.fps = fps
pygame.display.set_caption("Bubble sort animation - made by Big Shuang")
self.bmlist = []
for i in range(1, count):
x = WIN_WIDTH // count * i
bm = BubbleManager(self.win, x, WIN_HEIGHT)
self.bmlist .append(bm)
self.start = False
def loop(self):
while True:
# 获取所有事件
for event in pygame.event.get():
if event.type == pygame.QUIT:
# 判断当前事件是否为点击右上角退出键
pygame.quit()
if event.type == pygame.KEYDOWN:
# 点击任意键开始动画
self.start = True
if self.start:
for bm in self.bmlist :
if bm.bubbling:
bm.bubble_j()
elif not bm.bubble_done():
bm.bubble_once()
else:
bm.j = -1
self.win.fill(COLORS["bg"])
for bm in self.bmlist:
bm.draw()
self.clock.tick(self.fps)
pygame.display.update()
最后用如下调用一下就好
if __name__ == '__main__':
gui = Gui(WIN_WIDTH, WIN_HEIGHT, 10)
gui.loop()
运行后初始状态截图如下