Exemplo n.º 1
0
    def add_scaling(self, arrows, syms, arrays):
        s_arrows = VMobject(
            TexMobject("2"), Vector([1, 1]).highlight(YELLOW), 
            TexMobject("="), Vector([2, 2]).highlight(WHITE)
        )
        s_arrows.arrange_submobjects(RIGHT)
        s_arrows.scale(0.75)
        s_arrows.next_to(arrows, DOWN)

        s_arrays = VMobject(
            TexMobject("2"), 
            matrix_to_mobject([3, -5]).highlight(YELLOW),
            TextMobject("="),
            matrix_to_mobject(["2(3)", "2(-5)"])
        )
        s_arrays.arrange_submobjects(RIGHT)
        s_arrays.scale(0.5)
        s_arrays.next_to(arrays, DOWN)

        s_syms = TexMobject(["2", "\\vec{\\textbf{v}}"])
        s_syms.split()[-1].highlight(YELLOW)
        s_syms.next_to(syms, DOWN)

        self.play(
            Write(s_arrows), Write(s_arrays), Write(s_syms),
            run_time = 2
        )
        self.dither()
Exemplo n.º 2
0
    def construct(self):
        words = map(TextMobject, [
            "Just brushing up",
            "Has yet to take the course",
            "Supplementing course concurrently",
        ])
        students = VMobject(*[
            Randolph(color = c)
            for c in BLUE_D, BLUE_C, BLUE_E
        ])
        modes = ["pondering", "speaking_looking_left", "sassy"]
        students.arrange_submobjects(RIGHT)
        students.scale(0.8)
        students.center().to_edge(DOWN)

        last_word, last_arrow = None, None
        for word, student, mode in zip(words, students.split(), modes):
            word.shift(2*UP)
            arrow = Arrow(word, student)
            if last_word:
                word_anim = Transform(last_word, word)
                arrow_anim = Transform(last_arrow, arrow)
            else:
                word_anim = Write(word, run_time = 1)
                arrow_anim = ShowCreation(arrow, submobject_mode = "one_at_a_time")
                last_word = word
                last_arrow = arrow
            self.play(
                word_anim, arrow_anim,
                ApplyMethod(student.change_mode, mode)
            )
            self.play(Blink(student))
            self.dither()
        self.dither()
Exemplo n.º 3
0
    def add_scaling(self, arrows, syms, arrays):
        s_arrows = VMobject(
            TexMobject("2"), Vector([1, 1]).highlight(YELLOW), 
            TexMobject("="), Vector([2, 2]).highlight(WHITE)
        )
        s_arrows.arrange_submobjects(RIGHT)
        s_arrows.scale(0.75)
        s_arrows.next_to(arrows, DOWN)

        s_arrays = VMobject(
            TexMobject("2"), 
            matrix_to_mobject([3, -5]).highlight(YELLOW),
            TextMobject("="),
            matrix_to_mobject(["2(3)", "2(-5)"])
        )
        s_arrays.arrange_submobjects(RIGHT)
        s_arrays.scale(0.75)
        s_arrays.next_to(arrays, DOWN)

        s_syms = TexMobject(["2", "\\vec{\\textbf{v}}"])
        s_syms.split()[-1].highlight(YELLOW)
        s_syms.next_to(syms, DOWN)

        self.play(
            Write(s_arrows), Write(s_arrays), Write(s_syms),
            run_time = 2
        )
        self.wait()
Exemplo n.º 4
0
    def construct(self):
        t_axis = NumberLine(
            numbers_with_elongated_ticks = [],
            x_min = 0,
            x_max = 10,
            color = WHITE,
        )
        det_axis = NumberLine(
            numbers_with_elongated_ticks = [],
            x_min = -2,
            x_max = 2,
            color = WHITE
        )
        det_axis.rotate(np.pi/2)
        t_axis.next_to(ORIGIN, RIGHT, buff = 0)
        det_axis.move_to(t_axis.get_left())
        axes = VMobject(det_axis, t_axis)
        graph = FunctionGraph(np.cos, x_min = 0, x_max = np.pi)
        graph.next_to(det_axis, RIGHT, buff = 0)
        graph.highlight(YELLOW)
        det_word = TextMobject("Det")
        det_word.next_to(det_axis, RIGHT, aligned_edge = UP)
        time_word = TextMobject("time")
        time_word.next_to(t_axis, UP)
        time_word.to_edge(RIGHT)
        everything = VMobject(axes, det_word, time_word, graph)
        everything.scale(1.5)

        self.add(axes, det_word, time_word)
        self.play(ShowCreation(
            graph, rate_func = None, run_time = 10
        ))
Exemplo n.º 5
0
 def construct(self):
     arrays = VMobject(
         *map(matrix_to_mobject, [[-2, 3], [1, 2], [2, -1], [4, 0]]))
     arrays.arrange_submobjects(buff=0.4)
     arrays.scale(2)
     self.play(Write(arrays))
     self.dither(2)
Exemplo n.º 6
0
    def construct(self):
        t_axis = NumberLine(
            numbers_with_elongated_ticks=[],
            x_min=0,
            x_max=10,
            color=WHITE,
        )
        det_axis = NumberLine(numbers_with_elongated_ticks=[],
                              x_min=-2,
                              x_max=2,
                              color=WHITE)
        det_axis.rotate(np.pi / 2)
        t_axis.next_to(ORIGIN, RIGHT, buff=0)
        det_axis.move_to(t_axis.get_left())
        axes = VMobject(det_axis, t_axis)
        graph = FunctionGraph(np.cos, x_min=0, x_max=np.pi)
        graph.next_to(det_axis, RIGHT, buff=0)
        graph.highlight(YELLOW)
        det_word = TextMobject("Det")
        det_word.next_to(det_axis, RIGHT, aligned_edge=UP)
        time_word = TextMobject("time")
        time_word.next_to(t_axis, UP)
        time_word.to_edge(RIGHT)
        everything = VMobject(axes, det_word, time_word, graph)
        everything.scale(1.5)

        self.add(axes, det_word, time_word)
        self.play(ShowCreation(graph, rate_func=None, run_time=10))
Exemplo n.º 7
0
    def handle_mathy(self, creatures):
        self.fade_all_but(creatures, 2)
        physy, compy, mathy = creatures

        v_color = YELLOW 
        w_color = BLUE
        sum_color = GREEN

        v_arrow = Vector([1, 1])
        w_arrow = Vector([2, 1])
        w_arrow.shift(v_arrow.get_end())
        sum_arrow = Vector(w_arrow.get_end())
        arrows = VMobject(v_arrow, w_arrow, sum_arrow)
        arrows.scale(0.7)
        arrows.to_edge(LEFT, buff = 2)

        v_array = matrix_to_mobject([3, -5])
        w_array = matrix_to_mobject([2, 1])
        sum_array = matrix_to_mobject(["3+2", "-5+1"])
        arrays = VMobject(
            v_array, TexMobject("+"), w_array, TexMobject("="), sum_array
        )
        arrays.arrange_submobjects(RIGHT)
        arrays.scale(0.75)
        arrays.to_edge(RIGHT).shift(UP)

        v_sym = TexMobject("\\vec{\\textbf{v}}")
        w_sym = TexMobject("\\vec{\\textbf{w}}")
        syms = VMobject(v_sym, TexMobject("+"), w_sym)
        syms.arrange_submobjects(RIGHT)
        syms.center().shift(2*UP)

        statement = TextMobject("We'll ignore him \\\\ for now")
        statement.highlight(PINK)
        statement.scale_to_fit_width(arrays.get_width())
        statement.next_to(arrays, DOWN, buff = 1.5)
        circle = Circle()
        circle.shift(syms.get_bottom())

        VMobject(v_arrow, v_array, v_sym).highlight(v_color)
        VMobject(w_arrow, w_array, w_sym).highlight(w_color)
        VMobject(sum_arrow, sum_array).highlight(sum_color)

        self.play(
            Write(syms), Write(arrays),
            ShowCreation(arrows, submobject_mode = "one_at_a_time"),
            ApplyMethod(mathy.change_mode, "pondering"),
            run_time = 2
        )
        self.play(Blink(mathy))
        self.add_scaling(arrows, syms, arrays)
        self.play(Write(statement))
        self.play(ApplyMethod(mathy.change_mode, "sad"))
        self.dither()
        self.play(
            ShowCreation(circle),
            ApplyMethod(mathy.change_mode, "plain")
        )
        self.dither()
Exemplo n.º 8
0
    def handle_mathy(self, creatures):
        self.fade_all_but(creatures, 2)
        physy, compy, mathy = creatures

        v_color = YELLOW 
        w_color = BLUE
        sum_color = GREEN

        v_arrow = Vector([1, 1])
        w_arrow = Vector([2, 1])
        w_arrow.shift(v_arrow.get_end())
        sum_arrow = Vector(w_arrow.get_end())
        arrows = VMobject(v_arrow, w_arrow, sum_arrow)
        arrows.scale(0.7)
        arrows.to_edge(LEFT, buff = 2)

        v_array = matrix_to_mobject([3, -5])
        w_array = matrix_to_mobject([2, 1])
        sum_array = matrix_to_mobject(["3+2", "-5+1"])
        arrays = VMobject(
            v_array, TexMobject("+"), w_array, TexMobject("="), sum_array
        )
        arrays.arrange_submobjects(RIGHT)
        arrays.scale(0.75)
        arrays.to_edge(RIGHT).shift(UP)

        v_sym = TexMobject("\\vec{\\textbf{v}}")
        w_sym = TexMobject("\\vec{\\textbf{w}}")
        syms = VMobject(v_sym, TexMobject("+"), w_sym)
        syms.arrange_submobjects(RIGHT)
        syms.center().shift(2*UP)

        statement = TextMobject("We'll ignore him \\\\ for now")
        statement.highlight(PINK)
        statement.scale_to_fit_width(arrays.get_width())
        statement.next_to(arrays, DOWN, buff = 1.5)
        circle = Circle()
        circle.shift(syms.get_bottom())

        VMobject(v_arrow, v_array, v_sym).highlight(v_color)
        VMobject(w_arrow, w_array, w_sym).highlight(w_color)
        VMobject(sum_arrow, sum_array).highlight(sum_color)

        self.play(
            Write(syms), Write(arrays),
            ShowCreation(arrows, submobject_mode = "one_at_a_time"),
            ApplyMethod(mathy.change_mode, "pondering"),
            run_time = 2
        )
        self.play(Blink(mathy))
        self.add_scaling(arrows, syms, arrays)
        self.play(Write(statement))
        self.play(ApplyMethod(mathy.change_mode, "sad"))
        self.wait()
        self.play(
            ShowCreation(circle),
            ApplyMethod(mathy.change_mode, "plain")
        )
        self.wait()
Exemplo n.º 9
0
 def construct(self):
     arrays = VMobject(*map(matrix_to_mobject, [
         [-2, 3], [1, 2], [2, -1], [4, 0]
     ]))
     arrays.arrange_submobjects(buff = 0.4)
     arrays.scale(2)
     self.play(Write(arrays))
     self.dither(2)
Exemplo n.º 10
0
    def construct(self):
        v = TexMobject(self.v_str)
        v.highlight(YELLOW)
        eq = TexMobject("=")
        coords = Matrix(["x", "y", "z"])
        eq2 = eq.copy()
        if self.post_transform:
            L, l_paren, r_paren = map(TexMobject, "L()")
            parens = VMobject(l_paren, r_paren)
            parens.scale(2)
            parens.stretch_to_fit_height(
                coords.get_height()
            )
            VMobject(L, l_paren, coords, r_paren).arrange_submobjects(buff = 0.1)
            coords.submobjects = [L, l_paren] + coords.submobjects + [r_paren]

        lin_comb = VMobject(*map(TexMobject, [
            "x", self.i_str, "+",
            "y", self.j_str, "+",
            "z", self.k_str,
        ]))
        lin_comb.arrange_submobjects(
            RIGHT, buff = 0.1, 
            aligned_edge = ORIGIN if self.post_transform else DOWN
        )
        lin_comb_parts = np.array(lin_comb.split())
        new_x, new_y, new_z = lin_comb_parts[[0, 3, 6]]
        i, j, k = lin_comb_parts[[1, 4, 7]]
        plusses = lin_comb_parts[[2, 5]]
        i.highlight(X_COLOR)
        j.highlight(Y_COLOR)
        k.highlight(Z_COLOR)

        everything = VMobject(v, eq, coords, eq2, lin_comb)
        everything.arrange_submobjects(buff = 0.2)
        everything.scale_to_fit_width(2*SPACE_WIDTH - 1)
        everything.to_edge(DOWN)
        if not self.post_transform:
            lin_comb.shift(0.35*UP)

        self.play(*map(Write, [v, eq, coords]))
        self.dither()
        self.play(
            Transform(
                coords.get_entries().copy(),
                VMobject(new_x, new_y, new_z),
                path_arc = -np.pi,
                submobject_mode = "lagged_start"
            ),
            Write(VMobject(*[eq2, i, j, k] + list(plusses))),
            run_time = 3
        )
        self.dither()
Exemplo n.º 11
0
    def construct(self):
        title = TextMobject("Essence of Linear Algebra")
        title.highlight(BLUE)
        title.to_corner(UP+LEFT)
        h_line = Line(SPACE_WIDTH*LEFT, SPACE_WIDTH*RIGHT)
        h_line.next_to(title, DOWN)
        h_line.to_edge(LEFT, buff = 0)
        chapters = VMobject(*map(TextMobject, [
            "Chapter 1: Vectors, what even are they?",
            "Chapter 2: Linear combinations, span and bases",
            "Chapter 3: Matrices as linear transformations",
            "Chapter 4: Matrix multiplication as composition",
            "Chapter 5: The determinant",
            "Chapter 6: Inverse matrices, column space and null space",
            "Chapter 7: Dot products and cross products",
            "Chapter 8: Change of basis",
            "Chapter 9: Eigenvectors and eigenvalues",
            "Chapter 10: Abstract vector spaces",
        ]))
        chapters.arrange_submobjects(DOWN)
        chapters.scale(0.7)
        chapters.next_to(h_line, DOWN)

        self.play(
            Write(title),
            ShowCreation(h_line)
        )
        for chapter in chapters.split():
            chapter.to_edge(LEFT, buff = 1)
            self.play(FadeIn(chapter))
        self.dither(2)

        entry3 = chapters.split()[2]
        added_words = TextMobject("(Personally, I'm most excited \\\\ to do this one)")
        added_words.scale(0.5)
        added_words.highlight(YELLOW)
        added_words.next_to(h_line, DOWN)
        added_words.to_edge(RIGHT)
        arrow = Arrow(added_words.get_bottom(), entry3)

        self.play(
            ApplyMethod(entry3.highlight, YELLOW),
            ShowCreation(arrow, submobject_mode = "one_at_a_time"),
            Write(added_words),
            run_time = 1
        )
        self.dither()
        removeable = VMobject(added_words, arrow, h_line, title)
        self.play(FadeOut(removeable))
        self.remove(removeable)

        self.series_of_videos(chapters)
Exemplo n.º 12
0
    def construct(self):
        title = TextMobject("Essence of Linear Algebra")
        title.highlight(BLUE)
        title.to_corner(UP + LEFT)
        h_line = Line(SPACE_WIDTH * LEFT, SPACE_WIDTH * RIGHT)
        h_line.next_to(title, DOWN)
        h_line.to_edge(LEFT, buff=0)
        chapters = VMobject(*map(TextMobject, [
            "Chapter 1: Vectors, what even are they?",
            "Chapter 2: Linear combinations, span and bases",
            "Chapter 3: Matrices as linear transformations",
            "Chapter 4: Matrix multiplication as composition",
            "Chapter 5: The determinant",
            "Chapter 6: Inverse matrices, column space and null space",
            "Chapter 7: Dot products and cross products",
            "Chapter 8: Change of basis",
            "Chapter 9: Eigenvectors and eigenvalues",
            "Chapter 10: Abstract vector spaces",
        ]))
        chapters.arrange_submobjects(DOWN)
        chapters.scale(0.7)
        chapters.next_to(h_line, DOWN)

        self.play(Write(title), ShowCreation(h_line))
        for chapter in chapters.split():
            chapter.to_edge(LEFT, buff=1)
            self.play(FadeIn(chapter))
        self.wait(2)

        entry3 = chapters.split()[2]
        added_words = TextMobject(
            "(Personally, I'm most excited \\\\ to do this one)")
        added_words.scale(0.5)
        added_words.highlight(YELLOW)
        added_words.next_to(h_line, DOWN)
        added_words.to_edge(RIGHT)
        arrow = Arrow(added_words.get_bottom(), entry3)

        self.play(ApplyMethod(entry3.highlight, YELLOW),
                  ShowCreation(arrow, submobject_mode="one_at_a_time"),
                  Write(added_words),
                  run_time=1)
        self.wait()
        removeable = VMobject(added_words, arrow, h_line, title)
        self.play(FadeOut(removeable))
        self.remove(removeable)

        self.series_of_videos(chapters)
Exemplo n.º 13
0
    def construct(self):
        morty = Mortimer(mode = "speaking")
        morty.to_corner(DOWN + RIGHT)
        bubble = morty.get_bubble(SpeechBubble)
        bubble.write("I'm assuming you \\\\ know linear algebra\\dots")
        words = bubble.content
        bubble.clear()
        randys = VMobject(*[
            Randolph(color = c)
            for c in BLUE_D, BLUE_C, BLUE_E
        ])
        randys.arrange_submobjects(RIGHT)
        randys.scale(0.8)
        randys.to_corner(DOWN+LEFT)

        self.add(randys, morty)
        self.play(FadeIn(bubble), Write(words), run_time = 3)
        for randy in np.array(randys.split())[[2,0,1]]:
            self.play(Blink(randy))
        self.dither()
Exemplo n.º 14
0
    def construct(self):
        self.setup()
        self.plane.fade(0.3)
        self.add_unit_square(color = YELLOW_E, opacity = 0.5)
        self.add_title(
            ["The", "``determinant''", "of a transformation"],
            scale_factor = 1
        )
        self.title.split()[1].split()[1].highlight(YELLOW)

        matrix_background, matrix, det_text = self.get_matrix()
        self.add_foreground_mobject(matrix_background, matrix)

        A = TexMobject("A")
        area_label = VMobject(A.copy(), A.copy(), A)
        area_label.move_to(self.square)
        det = np.linalg.det(self.t_matrix)
        if np.round(det) == det:
            det = int(det)
        area_label_target = VMobject(
            TexMobject(str(det)), TexMobject("\\cdot"), A.copy()
        )
        if det < 1 and det > 0:
            area_label_target.scale(det)
        area_label_target.arrange_submobjects(RIGHT, buff = 0.1)
        self.add_moving_mobject(area_label, area_label_target)
        
        self.dither()
        self.apply_transposed_matrix(self.t_matrix)
        self.dither()
        det_mob_copy = area_label.split()[0].copy()
        new_det_mob = det_mob_copy.copy().scale_to_fit_height(
            det_text.split()[0].get_height()
        )
        new_det_mob.next_to(det_text, RIGHT, buff = 0.2)
        new_det_mob.add_background_rectangle()
        det_mob_copy.add_background_rectangle(opacity = 0)
        self.play(Write(det_text))
        self.play(Transform(det_mob_copy, new_det_mob))
        self.dither()
Exemplo n.º 15
0
    def construct(self):
        self.setup()
        self.plane.fade(0.3)
        self.add_unit_square(color=YELLOW_E, opacity=0.5)
        self.add_title(["The", "``determinant''", "of a transformation"],
                       scale_factor=1)
        self.title.split()[1].split()[1].highlight(YELLOW)

        matrix_background, matrix, det_text = self.get_matrix()
        self.add_foreground_mobject(matrix_background, matrix)

        A = TexMobject("A")
        area_label = VMobject(A.copy(), A.copy(), A)
        area_label.move_to(self.square)
        det = np.linalg.det(self.t_matrix)
        if np.round(det) == det:
            det = int(det)
        area_label_target = VMobject(TexMobject(str(det)),
                                     TexMobject("\\cdot"), A.copy())
        if det < 1 and det > 0:
            area_label_target.scale(det)
        area_label_target.arrange_submobjects(RIGHT, buff=0.1)
        self.add_moving_mobject(area_label, area_label_target)

        self.wait()
        self.apply_transposed_matrix(self.t_matrix)
        self.wait()
        det_mob_copy = area_label.split()[0].copy()
        new_det_mob = det_mob_copy.copy().scale_to_fit_height(
            det_text.split()[0].get_height())
        new_det_mob.next_to(det_text, RIGHT, buff=0.2)
        new_det_mob.add_background_rectangle()
        det_mob_copy.add_background_rectangle(opacity=0)
        self.play(Write(det_text))
        self.play(Transform(det_mob_copy, new_det_mob))
        self.wait()
Exemplo n.º 16
0
class TeacherStudentsScene(Scene):
    def setup(self):
        self.teacher = Mortimer()
        self.teacher.to_corner(DOWN + RIGHT)
        self.teacher.look(DOWN+LEFT)
        self.students = VMobject(*[
            Randolph(color = c)
            for c in BLUE_D, BLUE_C, BLUE_E
        ])
        self.students.arrange_submobjects(RIGHT)
        self.students.scale(0.8)
        self.students.to_corner(DOWN+LEFT)
        self.students = self.students.split()

        for pi_creature in self.get_everyone():
            pi_creature.bubble = None
        self.add(*self.get_everyone())

    def get_teacher(self):
        return self.teacher

    def get_students(self):
        return self.students

    def get_everyone(self):
        return [self.get_teacher()] + self.get_students()

    def get_bubble_intro_animation(self, content, bubble_type,
                                   pi_creature,
                                   **bubble_kwargs):
        bubble = pi_creature.get_bubble(bubble_type, **bubble_kwargs)
        bubble.add_content(content)
        if pi_creature.bubble:
            content_intro_anims = [
                Transform(pi_creature.bubble, bubble),
                Transform(pi_creature.bubble.content, bubble.content)
            ]
        else:
            content_intro_anims = [
                FadeIn(bubble),
                Write(content),
            ]
            pi_creature.bubble = bubble 
        return content_intro_anims

    def introduce_bubble(self, content, bubble_type, pi_creature,
                         pi_creature_target_mode = None,
                         added_anims = [],
                         **bubble_kwargs):
        if all(map(lambda s : isinstance(s, str), content)):
            content = TextMobject(*content)
        elif len(content) == 1 and isinstance(content[0], TexMobject):
            content = content[0]
        else:
            raise Exception("Invalid content type")
        content_intro_anims = self.get_bubble_intro_animation(
            content, bubble_type, pi_creature, **bubble_kwargs
        )

        if not pi_creature_target_mode:
            if bubble_type is "speech":
                pi_creature_target_mode = "speaking"
            else:
                pi_creature_target_mode = "pondering"

        for p in self.get_everyone():
            if p.bubble and p is not pi_creature:
                added_anims += [
                    FadeOut(p.bubble),
                    FadeOut(p.bubble.content)
                ]
                p.bubble = None
                added_anims.append(ApplyMethod(p.change_mode, "plain"))

        anims = added_anims + content_intro_anims + [
            ApplyMethod(
                pi_creature.change_mode, 
                pi_creature_target_mode,
            ),
        ]
        self.play(*anims)
        return pi_creature.bubble

    def teacher_says(self, *content, **kwargs):
        return self.introduce_bubble(
            content, "speech", self.get_teacher(), **kwargs
        )

    def student_says(self, *content, **kwargs):
        student = self.get_students()[kwargs.get("student_index", 1)]
        return self.introduce_bubble(content, "speech", student, **kwargs)

    def teacher_thinks(self, *content, **kwargs):
        return self.introduce_bubble(
            content, "thought", self.get_teacher(), **kwargs
        )

    def student_thinks(self, *content, **kwargs):
        student = self.get_students()[kwargs.get("student_index", 1)]
        return self.introduce_bubble(content, "thought", student, **kwargs)

    def random_blink(self, num_times = 1):
        for x in range(num_times):
            pi_creature = random.choice(self.get_everyone())
            self.play(Blink(pi_creature))
            self.dither()

    def change_student_modes(self, *modes):
        self.play(*[
            ApplyMethod(pi.change_mode, mode)
            for pi, mode in zip(self.get_students(), modes)
        ])

    def zoom_in_on_thought_bubble(self, radius = SPACE_HEIGHT+SPACE_WIDTH):
        bubble = None
        for pi in self.get_everyone():
            if hasattr(pi, "bubble") and isinstance(pi.bubble, ThoughtBubble):
                bubble = pi.bubble
                break
        if bubble is None:
            raise Exception("No pi creatures have a thought bubble")
        vect = -bubble.get_bubble_center()
        def func(point):
            centered = point+vect
            return radius*centered/np.linalg.norm(centered)
        self.play(*[
            ApplyPointwiseFunction(func, mob)
            for mob in self.get_mobjects()
        ])
Exemplo n.º 17
0
class TeacherStudentsScene(Scene):
    def setup(self):
        self.teacher = Mortimer()
        self.teacher.to_corner(DOWN + RIGHT)
        self.teacher.look(DOWN+LEFT)
        self.students = VMobject(*[
            Randolph(color = c)
            for c in BLUE_D, BLUE_C, BLUE_E
        ])
        self.students.arrange_submobjects(RIGHT)
        self.students.scale(0.8)
        self.students.to_corner(DOWN+LEFT)

        for pi_creature in self.get_everyone():
            pi_creature.bubble = None
        self.add(*self.get_everyone())

    def get_teacher(self):
        return self.teacher

    def get_students(self):
        return self.students.split()

    def get_everyone(self):
        return [self.get_teacher()] + self.get_students()

    def get_bubble_intro_animation(self, content, bubble_type,
                                   pi_creature,
                                   **bubble_kwargs):
        bubble = pi_creature.get_bubble(bubble_type, **bubble_kwargs)
        bubble.add_content(content)
        if pi_creature.bubble:
            content_intro_anims = [
                Transform(pi_creature.bubble, bubble),
                Transform(pi_creature.bubble.content, bubble.content)
            ]
        else:
            content_intro_anims = [
                FadeIn(bubble),
                Write(content),
            ]
            pi_creature.bubble = bubble 
        return content_intro_anims

    def introduce_bubble(self, content, bubble_type, pi_creature,
                         pi_creature_target_mode = None,
                         added_anims = [],
                         **bubble_kwargs):
        if isinstance(content, str):
            content = TextMobject(content)     
        content_intro_anims = self.get_bubble_intro_animation(
            content, bubble_type, pi_creature, **bubble_kwargs
        )

        if not pi_creature_target_mode:
            if bubble_type is "speech":
                pi_creature_target_mode = "speaking"
            else:
                pi_creature_target_mode = "pondering"

        for p in self.get_everyone():
            if p.bubble and p is not pi_creature:
                added_anims += [
                    FadeOut(p.bubble),
                    FadeOut(p.bubble.content)
                ]
                p.bubble = None
                added_anims.append(ApplyMethod(p.change_mode, "plain"))

        anims = added_anims + content_intro_anims + [
            ApplyMethod(
                pi_creature.change_mode, 
                pi_creature_target_mode,
            ),
        ]
        self.play(*anims)
        return pi_creature.bubble

    def teacher_says(self, content = "", **kwargs):
        return self.introduce_bubble(
            content, "speech", self.get_teacher(), **kwargs
        )

    def student_says(self, content = "", student_index = 1, **kwargs):
        student = self.get_students()[student_index]
        return self.introduce_bubble(content, "speech", student, **kwargs)

    def teacher_thinks(self, content = "", **kwargs):
        return self.introduce_bubble(
            content, "thought", self.get_teacher(), **kwargs
        )

    def student_thinks(self, content = "", student_index = 1, **kwargs):
        student = self.get_students()[student_index]
        return self.introduce_bubble(content, "thought", student, **kwargs)

    def random_blink(self, num_times = 1):
        for x in range(num_times):
            pi_creature = random.choice(self.get_everyone())
            self.play(Blink(pi_creature))
            self.dither()