From 9d5282b74f4d40c5c9e5da1d1624d99b0bcaeb89 Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Sat, 13 Jun 2020 15:19:42 -0700 Subject: [PATCH] New scene for chessboard video --- from_3b1b/active/chess.py | 288 +++++++++++++++++++++++++++++++++++--- 1 file changed, 268 insertions(+), 20 deletions(-) diff --git a/from_3b1b/active/chess.py b/from_3b1b/active/chess.py index 94d41794..a96fa287 100644 --- a/from_3b1b/active/chess.py +++ b/from_3b1b/active/chess.py @@ -1,12 +1,24 @@ from manimlib.imports import * +def boolian_linear_combo(bools): + return reduce(op.xor, [b * n for n, b in enumerate(bools)], 0) + + +def string_to_bools(message): + # For easter eggs on the board + bits = bin(int.from_bytes(message.encode(), 'big'))[2:] + bits = (len(message) * 8 - len(bits)) * '0' + bits + return [bool(int(b)) for b in bits] + + class Chessboard(SGroup): CONFIG = { "shape": (8, 8), "height": 7, "depth": 0.25, - "colors": [LIGHT_GREY, DARKER_GREY] + "colors": [LIGHT_GREY, DARKER_GREY], + "gloss": 0.2, } def __init__(self, **kwargs): @@ -25,11 +37,12 @@ class Chessboard(SGroup): color = self.colors[(i + j) % 2] self[i * nc + j].set_color(color) self.center() + self.set_gloss(self.gloss) class Coin(Group): CONFIG = { - "n_sides": 24, + "disk_resolution": (4, 51), "height": 1, "depth": 0.1, "color": GOLD_D, @@ -39,11 +52,14 @@ class Coin(Group): def __init__(self, **kwargs): super().__init__(**kwargs) - n = self.n_sides + 1 - self.top = Disk3D(resolution=(4, n)).shift(OUT) - self.bottom = Disk3D(resolution=(4, n)).shift(IN) - self.edge = Cylinder(height=2, resolution=(n, 2)) + res = self.disk_resolution + self.top = Disk3D(resolution=res, gloss=0.2) + self.bottom = self.top.copy() + self.top.shift(OUT) + self.bottom.shift(IN) + self.edge = Cylinder(height=2, resolution=(res[1], 2)) self.add(self.top, self.bottom, self.edge) + self.rotate(90 * DEGREES, OUT) self.set_color(self.color) self.bottom.set_color(RED) @@ -62,6 +78,9 @@ class Coin(Group): self.set_height(self.height) self.set_depth(self.depth, stretch=True) + def is_heads(self): + return self.top.get_center()[2] > self.bottom.get_center()[2] + class CoinsOnBoard(Group): CONFIG = { @@ -104,40 +123,269 @@ class FlipCoin(Animation): coin.shift(4 * alpha * (1 - alpha) * self.shift_vect) return coin -# Scenes +# Scenes class IntroducePuzzle(Scene): CONFIG = { - "camera_config": { - "apply_depth_test": True, - "samples": 8, - } + "camera_class": ThreeDCamera, } def construct(self): + # Setup frame = self.camera.frame chessboard = Chessboard() chessboard.move_to(ORIGIN, OUT) - plane = NumberPlane( + grid = NumberPlane( x_range=(0, 8), y_range=(0, 8), - faded_line_ratio=1, + faded_line_ratio=0 ) + grid.match_height(chessboard) + grid.next_to(chessboard, OUT, 1e-8) + low_grid = grid.copy() + low_grid.next_to(chessboard, IN, 1e-8) + grid.add(low_grid) + grid.set_stroke(GREY, width=2) + grid.set_gloss(0.5) + grid.prepare_for_nonlinear_transform(0) coins = CoinsOnBoard(chessboard, include_labels=False) - coins_random_order = Group(*coins) - coins_random_order.shuffle() + coins.set_gloss(0.2) + coins_to_flip = Group() + head_bools = string_to_bools('3b1b :)') + for coin, heads in zip(coins, head_bools): + if not heads: + coins_to_flip.add(coin) + coins_to_flip.shuffle() - frame.set_phi(45 * DEGREES) + count_label = VGroup( + Integer(0, edge_to_fix=RIGHT), + TextMobject("Coins") + ) + count_label.arrange(RIGHT, aligned_edge=DOWN) + count_label.to_corner(UL) + count_label.fix_in_frame() - self.add(chessboard) - self.play(LaggedStartMap(FadeIn, coins_random_order, lambda m: (m, UP), run_time=1)) + # Draw board and coins + frame.set_rotation(-25 * DEGREES, 70 * DEGREES, 0) + self.play( + FadeIn(chessboard), + ShowCreationThenDestruction(grid, lag_ratio=0.01), + frame.set_theta, 0, + frame.set_phi, 45 * DEGREES, + run_time=3, + ) + self.wait() + + self.add(count_label) + self.play( + ShowIncreasingSubsets(coins), + UpdateFromFunc(count_label[0], lambda m, c=coins: m.set_value(len(c))), + rate_func=bezier([0, 0, 1, 1]), + run_time=2, + ) + self.wait() + self.play(LaggedStartMap(FlipCoin, coins_to_flip, run_time=6, lag_ratio=0.1)) self.add(coins) + coins.unlock_shader_data() self.wait() - coins_to_flip = Group(*[c for c in coins_random_order if random.random() < 0.5]) - self.play(LaggedStartMap(FlipCoin, coins_to_flip, run_time=1, lag_ratio=0.05)) + # Show key + key = SVGMobject("key") + key.set_stroke(GOLD, 0) + key.set_fill(GOLD, 1) + key.set_gloss(0.5) + key.rotate(PI / 2, OUT) + key.rotate(PI / 4, RIGHT) + key.move_to(3 * OUT) + key.scale(0.8) + key.to_edge(LEFT, buff=1) + + k = boolian_linear_combo(head_bools) ^ 63 # To make the flip below the actual solution + key_cube = Cube(resolution=(6, 6)) + key_cube.match_color(chessboard[k]) + key_cube.replace(chessboard[k], stretch=True) + chessboard.replace_submobject(k, key_cube) + key_square = key_cube[0] + chessboard.generate_target() + chessboard.save_state() + for i, cube in enumerate(chessboard.target): + if i == k: + cube[0].set_color(MAROON_E) + else: + cube.set_color(interpolate_color(cube.get_color(), BLACK, 0.8)) + + key.generate_target() + key.target.rotate(PI / 4, LEFT) + key.target.set_width(0.7 * key_square.get_width()) + key.target.next_to(key_square, IN, buff=SMALL_BUFF) + + self.play(FadeIn(key, LEFT)) self.wait() + self.play( + FadeOut(coins, lag_ratio=0.01), + MoveToTarget(chessboard), + ) + ks_top = key_square.get_top() + self.play( + Rotate(key_square, PI / 2, axis=LEFT, about_point=ks_top), + MoveToTarget(key), + frame.set_phi, 60 * DEGREES, + run_time=2, + ) + self.play( + Rotate(key_square, PI / 2, axis=RIGHT, about_point=ks_top), + run_time=2, + ) + chessboard.unlock_shader_data() + chessboard.saved_state[k][0].match_color(key_square) + self.play( + chessboard.restore, + FadeIn(coins), + frame.set_phi, 20 * DEGREES, + frame.move_to, 2 * LEFT, + run_time=3 + ) + + # State goal + goal = TextMobject( + "Communicate where\\\\the key is", + " by turning\\\\over one coin.", + alignment="" + ) + goal.next_to(count_label, DOWN, LARGE_BUFF, LEFT) + goal.fix_in_frame() + goal[1].set_color(YELLOW) + + self.play(FadeIn(goal[0])) + self.wait() + self.play(FadeIn(goal[1])) + self.wait() + + coin = coins[63] + rect = SurroundingRectangle(coin, color=TEAL) + + self.play(FadeInFromLarge(rect)) + self.play(FlipCoin(coin), FadeOut(rect)) + + +class FromCoinToSquareMaps(Scene): + CONFIG = { + "camera_class": ThreeDCamera, + "messages": [ + "Please, ", + "go watch", + "Stand-up", + "Maths on", + "YouTube." + ], + "flip_lag_ratio": 0.05, + } + + def construct(self): + messages = self.messages + + board1 = Chessboard() + board1.set_width(5.5) + board1.to_corner(DL) + + board2 = board1.copy() + board2.to_corner(DR) + + coins = CoinsOnBoard(board1) + bools = string_to_bools(messages[0]) + for coin, head in zip(coins, bools): + if not head: + coin.flip(RIGHT) + + for cube in board2: + cube.original_color = cube.get_color() + + arrow = Arrow(board1.get_right(), board2.get_left()) + arrow.tip.set_stroke(width=0) + + title1 = TextMobject("Pattern of coins") + title2 = TextMobject("Individual square") + + for title, board in [(title1, board1), (title2, board2)]: + title.scale(0.5 / title[0][0].get_height()) + title.next_to(board, UP, MED_LARGE_BUFF) + + title2.align_to(title1, UP) + + def get_special_square(coins=coins, board=board2): + bools = [coin.is_heads() for coin in coins] + return board[boolian_linear_combo(bools)] + + self.add(board1) + self.add(title1) + self.add(coins) + + self.play( + GrowArrow(arrow), + FadeIn(board2, 2 * LEFT) + ) + square = get_special_square() + rect = SurroundingRectangle(square, buff=0) + rect.set_color(PINK) + rect.next_to(square, OUT, buff=0.01) + self.play( + square.set_color, MAROON_C, + ShowCreation(rect), + FadeIn(title2) + ) + + for message in messages[1:]: + new_bools = string_to_bools(message) + coins_to_flip = Group() + for coin, to_heads in zip(coins, new_bools): + if coin.is_heads() ^ to_heads: + coins_to_flip.add(coin) + coins_to_flip.shuffle() + self.play(LaggedStartMap( + FlipCoin, coins_to_flip, + lag_ratio=self.flip_lag_ratio, + run_time=1, + )) + + new_square = get_special_square() + self.play( + square.set_color, square.original_color, + new_square.set_color, MAROON_C, + rect.move_to, new_square, OUT, + rect.shift, 0.01 * OUT, + ) + square = new_square + self.wait() + + +class FromCoinToSquareMapsSingleFlips(FromCoinToSquareMaps): + CONFIG = { + "messages": [ + "FlipBits", + "BlipBits", + "ClipBits", + "ChipBits", + "ChipBats", + "ChipRats", + ] + } + + +class DiagramOfProgression(Scene): + def construct(self): + stages = VGroup(*[Rectangle(4, 2) for x in range(4)]) + stages.arrange_in_grid(buff=1) + stages.set_height(6) + stages.to_edge(DOWN) + + titles = VGroup( + TextMobject("Prisoners conspire"), + TextMobject("Prisoner 1 sees key"), + TextMobject("Prisoner 1 flips coin"), + ) + + self.add(stages) self.embed()