diff --git a/bubble-sort/main.py b/bubble-sort/main.py new file mode 100644 index 0000000..7279c91 --- /dev/null +++ b/bubble-sort/main.py @@ -0,0 +1,115 @@ +from typing import List + +from manim import * + + +class BubbleSort(Scene): + """ + A animation of Bubble Sort sorting algorithm. + + Very simple, a arrangement of boxes containing singular digits within them, slowly swapping values until + the array is sorted in increasing order. + """ + + def __init__(self, *args, **kwargs, ): + super().__init__(*args, **kwargs) + + self.array = [3, 8, 3, 6, 6, 4, 1, 4, 8, 4, 2, 7, 9, 8, 6, 8] + self.squares: List[VGroup] = [] + self.entire = VGroup() + self.size_multiplier = (14 - 1.5) / len(self.array) + self.arrow_cur = 0 + self.arrow_shown = False + + def construct(self): + """Main method.""" + + self.assemble() + self.add(self.entire) + self.entire.to_edge(LEFT, buff=0.75) + + running = True + while running: + running = self.tick() + + def assemble(self): + """Assemble all animation elements""" + for i, value in enumerate(self.array): + square = VGroup() + stroke = Square(stroke_color=BLUE, stroke_width=4 * self.size_multiplier, fill_opacity=0, stroke_opacity=1, + side_length=self.size_multiplier) + digit = Text(str(value), size=1.3 * self.size_multiplier) + + square.add(stroke, digit) + square.shift(RIGHT * self.size_multiplier * i) + + self.entire.add(square) + self.squares.append(square) + + bottom = self.squares[0].get_bottom() + self.arrow = Arrow(bottom + DOWN * self.size_multiplier, bottom, buff=0, fill_opacity=0) + self.entire.add(self.arrow) + + def tick(self) -> bool: + """ + Animates a single tick of the Bubble Sort sorting algorithm, swapping one value with another at most. + + :return: True if anything was swapped, otherwise False. + """ + for i in range(len(self.array) - 1): + if not self.arrow_shown and i == 0: + self.move_arrow(i, time=0) + self.show_arrow(True) + if self.array[i] > self.array[i + 1]: + self.move_arrow(i + 0.5, time=0.15 * i) + self.swap(i, i + 1) + self.show_arrow(False) + self.wait(0.2) + self.array[i], self.array[i + 1] = self.array[i + 1], self.array[i] + self.squares[i], self.squares[i + 1] = self.squares[i + 1], self.squares[i] + return True + + return False + + def move_arrow(self, i, time: float = 0.15) -> None: + """Move the arrow to a specific position around the array.""" + shift = RIGHT * (i - self.arrow_cur) * self.size_multiplier + if time == 0: + self.arrow.shift(shift) + else: + self.play(self.arrow.animate.shift(shift), run_time=time, rate_func=rate_functions.ease_in_out_sine) + self.arrow_cur = i + + def show_arrow(self, val: bool = True) -> None: + """Show or hide the arrow via it's opacity.""" + self.play( + self.arrow.animate.set_opacity(1 if val else 0), + run_time=0.05 + ) + + def swap(self, x: int, y: int) -> None: + """Swaps two positions in the array with each other.""" + dir = x < y + diff = abs(x - y) * self.size_multiplier + self.play( + self.squares[x].animate.shift(UP * self.size_multiplier * 1.1), + self.squares[y].animate.shift(DOWN * self.size_multiplier * 1.1), + run_time=0.12 + ) + + self.play( + self.squares[x].animate.shift((RIGHT if dir else LEFT) * diff), + self.squares[y].animate.shift((LEFT if dir else RIGHT) * diff), + run_time=0.2 + ) + + self.play( + self.squares[x].animate.shift(DOWN * self.size_multiplier * 1.1), + self.squares[y].animate.shift(UP * self.size_multiplier * 1.1), + run_time=0.12 + ) + pass + + def change_digit(self, pos: int, val: int) -> None: + """Change the digit present at a specific position in the array.""" + pass