manim-chess 0.0.1__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
src/board.py ADDED
@@ -0,0 +1,532 @@
1
+ from manim import *
2
+ import math
3
+ from typing import Tuple
4
+ from .pieces import Pawn, Knight, Bishop, Rook, Queen, King
5
+
6
+ class Board(Mobject):
7
+ """
8
+ A class to represent a chess board using Manim for visualization.
9
+
10
+ Attributes:
11
+ ----------
12
+ size_of_board : int
13
+ The number of squares along one side of the board (default is 8 for a standard chess board).
14
+ cell_size : float
15
+ The length of each square on the board.
16
+ squares : dict
17
+ A dictionary mapping coordinates (e.g., 'a1') to their corresponding Square objects.
18
+ pieces : dict
19
+ A dictionary mapping coordinates to their corresponding chess piece objects.
20
+ highlighted_squares : list
21
+ A list of coordinates of squares that are currently highlighted.
22
+ arrows : list
23
+ A list of arrow objects currently drawn on the board.
24
+
25
+ Methods:
26
+ -------
27
+ create_board():
28
+ Initializes the board with squares and labels.
29
+ add_number_label(square, number):
30
+ Adds a number label to a square.
31
+ add_letter_label(square, letter):
32
+ Adds a letter label to a square.
33
+ get_square(coordinate):
34
+ Returns the Square object at the given coordinate.
35
+ add_piece(piece_type, is_white, coordinate):
36
+ Adds a chess piece to the board at the specified coordinate.
37
+ get_piece_info_from_FEN(FEN):
38
+ Extracts piece placement information from a FEN string.
39
+ get_coordinate_from_index(index):
40
+ Converts a linear index to a board coordinate.
41
+ set_board_from_FEN(FEN):
42
+ Sets up the board pieces according to a FEN string.
43
+ is_light_square(coordinate):
44
+ Determines if a square is a light-colored square.
45
+ mark_square(coordinate):
46
+ Marks a square with a specific color.
47
+ unmark_square(coordinate):
48
+ Resets a square to its original color.
49
+ highlight_square(coordinate):
50
+ Highlights a square with a specific color.
51
+ get_arrow_buffer(end_position, tip_position):
52
+ Calculates buffer positions for drawing arrows.
53
+ draw_arrow(end_coordinate, tip_coordinate):
54
+ Draws an arrow between two squares.
55
+ remove_piece(coordinate):
56
+ Removes a piece from the board.
57
+ remove_arrows():
58
+ Removes all arrows from the board.
59
+ move_piece(starting_coordinate, ending_coordinate):
60
+ Moves a piece from one square to another.
61
+ promote_piece(coordinate, piece_type):
62
+ Promotes a piece to another piece type.
63
+ get_piece_at_square(coordinate):
64
+ Returns the piece at a given coordinate, if any.
65
+ """
66
+
67
+ def __init__(self) -> None:
68
+ """
69
+ Initializes the Board object.
70
+ """
71
+ super().__init__()
72
+ self.size_of_board = 8
73
+ self.cell_size = 0.8 # Size of each square in the board
74
+ self.squares = {} # squares[coordinate] = square
75
+ self.create_board()
76
+ self.pieces = {} # pieces[coordinate] = piece
77
+ self.highlighted_squares = []
78
+ self.arrows = []
79
+
80
+ def create_board(self) -> None:
81
+ """
82
+ Creates the chess board with squares and labels.
83
+ """
84
+ total_size = self.size_of_board * self.cell_size # Total size of the board
85
+ offset = total_size / 2 # Offset to center of board
86
+
87
+ letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
88
+
89
+ for row in range(self.size_of_board):
90
+ for col in range(self.size_of_board):
91
+ CHESS_GREEN = ManimColor('#769656')
92
+ CHESS_WHITE = ManimColor('#eeeed2')
93
+ color = CHESS_WHITE if (row + col) % 2 else CHESS_GREEN
94
+ # Create a square for each cell in the board
95
+ square = Square(side_length=self.cell_size)
96
+ square.set_fill(color, opacity=1)
97
+ square.set_stroke(color, opacity=0)
98
+ square.move_to(np.array([col * self.cell_size - offset, row * self.cell_size - offset, 0]))
99
+
100
+ # Add number label if first col
101
+ if col == 0:
102
+ self.add_number_label(square, row + 1)
103
+
104
+ # Add letter label if first row
105
+ if row == 0:
106
+ self.add_letter_label(square, letters[col])
107
+
108
+ # Add square to dictionary so we can access it with key
109
+ self.squares[f'{letters[col]}{row + 1}'] = square
110
+ # Add square to self mobj
111
+ self.add(square)
112
+
113
+ def add_number_label(self, square: Square, number: str) -> None:
114
+ """
115
+ Adds a number label to a square.
116
+
117
+ Parameters:
118
+ ----------
119
+ square : Square
120
+ The square to which the number label is added.
121
+ number : str
122
+ The number to be displayed on the square.
123
+ """
124
+ CHESS_GREEN = ManimColor('#769656')
125
+ CHESS_WHITE = ManimColor('#eeeed2')
126
+
127
+ offset = np.array([self.cell_size / 8, -self.cell_size / 6, 0])
128
+
129
+ number_color = CHESS_WHITE if square.fill_color == CHESS_GREEN else CHESS_GREEN
130
+ number = Text(f'{number}', color=number_color, font_size=14 * self.cell_size, font="Arial")
131
+ square_top_left = square.get_center() + np.array([-self.cell_size / 2, self.cell_size / 2, 0])
132
+ number.move_to(square_top_left + offset)
133
+ square.add(number)
134
+
135
+ def add_letter_label(self, square: Square, letter: str) -> None:
136
+ """
137
+ Adds a letter label to a square.
138
+
139
+ Parameters:
140
+ ----------
141
+ square : Square
142
+ The square to which the letter label is added.
143
+ letter : str
144
+ The letter to be displayed on the square.
145
+ """
146
+ CHESS_GREEN = ManimColor('#769656')
147
+ CHESS_WHITE = ManimColor('#eeeed2')
148
+
149
+ offset = np.array([-self.cell_size / 8, self.cell_size / 6, 0])
150
+
151
+ letter_color = CHESS_WHITE if square.fill_color == CHESS_GREEN else CHESS_GREEN
152
+ letter = Text(f'{letter}', color=letter_color, font_size=14 * self.cell_size, font="Arial")
153
+ square_bot_right = square.get_center() + np.array([self.cell_size / 2, -self.cell_size / 2, 0])
154
+ letter.move_to(square_bot_right + offset)
155
+ square.add(letter)
156
+
157
+ def get_square(self, coordinate: str) -> Square:
158
+ """
159
+ Returns the Square object at the given coordinate.
160
+
161
+ Parameters:
162
+ ----------
163
+ coordinate : str
164
+ The coordinate of the square (e.g., 'a1').
165
+
166
+ Returns:
167
+ -------
168
+ Square
169
+ The Square object at the specified coordinate.
170
+ """
171
+ return self.squares[coordinate]
172
+
173
+ def add_piece(self, piece_type: str, is_white: bool, coordinate: str) -> None:
174
+ """
175
+ Adds a chess piece to the board at the specified coordinate.
176
+
177
+ Parameters:
178
+ ----------
179
+ piece_type : str
180
+ The type of the piece (e.g., 'P' for Pawn).
181
+ is_white : bool
182
+ True if the piece is white, False if black.
183
+ coordinate : str
184
+ The coordinate where the piece is to be placed.
185
+ """
186
+ piece_classes = {
187
+ 'P': Pawn,
188
+ 'N': Knight,
189
+ 'B': Bishop,
190
+ 'R': Rook,
191
+ 'Q': Queen,
192
+ 'K': King,
193
+ }
194
+
195
+ piece_class = piece_classes.get(piece_type)
196
+ if piece_class:
197
+ piece = piece_class(is_white=is_white).move_to(self.squares[coordinate].get_center())
198
+ self.pieces[coordinate] = piece
199
+ self.add(piece)
200
+ else:
201
+ raise ValueError(f"Unknown piece type: {piece_type}")
202
+
203
+ def get_piece_info_from_FEN(self, FEN: str) -> str:
204
+ """
205
+ Extracts piece placement information from a FEN string.
206
+
207
+ Parameters:
208
+ ----------
209
+ FEN : str
210
+ The FEN string representing the board state.
211
+
212
+ Returns:
213
+ -------
214
+ str
215
+ The piece placement part of the FEN string.
216
+ """
217
+ return FEN.split()[0]
218
+
219
+ def get_coordinate_from_index(self, index: int) -> str:
220
+ """
221
+ Converts a linear index to a board coordinate.
222
+
223
+ Parameters:
224
+ ----------
225
+ index : int
226
+ The linear index of a square.
227
+
228
+ Returns:
229
+ -------
230
+ str
231
+ The board coordinate corresponding to the index.
232
+ """
233
+ number_to_letter = {
234
+ 0: 'a',
235
+ 1: 'b',
236
+ 2: 'c',
237
+ 3: 'd',
238
+ 4: 'e',
239
+ 5: 'f',
240
+ 6: 'g',
241
+ 7: 'h'
242
+ }
243
+ coordinate = f'{number_to_letter[index % 8]}{8 - math.floor(index / 8)}'
244
+ return coordinate
245
+
246
+ def set_board_from_FEN(self, FEN: str="rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1") -> None:
247
+ """
248
+ Sets up the board pieces according to a FEN string. Default FEN is the standard start of game.
249
+
250
+ Parameters:
251
+ ----------
252
+ FEN : str
253
+ The FEN string representing the board state.
254
+ """
255
+ piece_info = self.get_piece_info_from_FEN(FEN)
256
+ current_index = 0
257
+ for char in piece_info:
258
+ if char in {'1', '2', '3', '4', '5', '6', '7', '8'}:
259
+ current_index += int(char)
260
+ elif char == '/':
261
+ pass
262
+ else:
263
+ coordinate = self.get_coordinate_from_index(current_index)
264
+ self.add_piece(char.upper(), char.isupper(), coordinate)
265
+ current_index += 1
266
+
267
+ def is_light_square(self, coordinate: str) -> bool:
268
+ """
269
+ Determines if a square is a light-colored square.
270
+
271
+ Parameters:
272
+ ----------
273
+ coordinate : str
274
+ The coordinate of the square.
275
+
276
+ Returns:
277
+ -------
278
+ bool
279
+ True if the square is light-colored, False otherwise.
280
+ """
281
+ if coordinate[0] in {'a', 'c', 'e', 'g'}:
282
+ if coordinate[1] in {'1', '3', '5', '7'}:
283
+ return False
284
+ else:
285
+ return True
286
+ else:
287
+ if coordinate[1] in {'1', '3', '5', '7'}:
288
+ return True
289
+ else:
290
+ return False
291
+
292
+ def mark_square(self, coordinate: str) -> None:
293
+ """
294
+ Marks a square with a specific color.
295
+
296
+ Parameters:
297
+ ----------
298
+ coordinate : str
299
+ The coordinate of the square to be marked.
300
+ """
301
+ MARK_COLOR = ManimColor('#EC7D6A')
302
+ self.squares[coordinate].set_fill(MARK_COLOR)
303
+
304
+ # Add back text on square if needed
305
+ if coordinate[1] == '1':
306
+ self.add_letter_label(self.squares[coordinate], coordinate[0])
307
+ if coordinate[0] == 'a':
308
+ self.add_number_label(self.squares[coordinate], coordinate[1])
309
+
310
+ def unmark_square(self, coordinate: str) -> None:
311
+ """
312
+ Resets a square to its original color.
313
+
314
+ Parameters:
315
+ ----------
316
+ coordinate : str
317
+ The coordinate of the square to be unmarked.
318
+ """
319
+ CHESS_GREEN = ManimColor('#769656')
320
+ CHESS_WHITE = ManimColor('#eeeed2')
321
+
322
+ if self.is_light_square(coordinate):
323
+ self.squares[coordinate].set_fill(CHESS_WHITE)
324
+ else:
325
+ self.squares[coordinate].set_fill(CHESS_GREEN)
326
+
327
+ # Add back text on square if needed
328
+ if coordinate[1] == '1':
329
+ self.add_letter_label(self.squares[coordinate], coordinate[0])
330
+ if coordinate[0] == 'a':
331
+ self.add_number_label(self.squares[coordinate], coordinate[1])
332
+
333
+ def highlight_square(self, coordinate: str) -> None:
334
+ """
335
+ Highlights a square with a specific color.
336
+
337
+ Parameters:
338
+ ----------
339
+ coordinate : str
340
+ The coordinate of the square to be highlighted.
341
+ """
342
+ LIGHT_HIGHLIGHT_COLOR = ManimColor('#F7F769')
343
+ DARK_HIGHLIGHT_COLOR = ManimColor('#BBCB2B')
344
+
345
+ if self.is_light_square(coordinate):
346
+ self.squares[coordinate].set_fill(LIGHT_HIGHLIGHT_COLOR)
347
+ else:
348
+ self.squares[coordinate].set_fill(DARK_HIGHLIGHT_COLOR)
349
+
350
+ # Add back text on square if needed
351
+ if coordinate[1] == '1':
352
+ self.add_letter_label(self.squares[coordinate], coordinate[0])
353
+ if coordinate[0] == 'a':
354
+ self.add_number_label(self.squares[coordinate], coordinate[1])
355
+
356
+ def get_arrow_buffer(self, end_position: np.array, tip_position: np.array) -> Tuple[np.array]:
357
+ """
358
+ Calculates buffer positions for drawing arrows.
359
+
360
+ Parameters:
361
+ ----------
362
+ end_position : np.array
363
+ The end position of the arrow.
364
+ tip_position : np.array
365
+ The tip position of the arrow.
366
+
367
+ Returns:
368
+ -------
369
+ Tuple[np.array]
370
+ The buffered end and tip positions.
371
+ """
372
+ # Calculate the direction vector
373
+ subtracted_position_vectors = end_position - tip_position
374
+ buffer = 0.25
375
+
376
+ # Calculate the normalized direction vector
377
+ direction_vector = subtracted_position_vectors / np.linalg.norm(subtracted_position_vectors)
378
+
379
+ end_position_buffer = direction_vector * buffer
380
+ tip_position_buffer = -direction_vector * buffer
381
+
382
+ return end_position_buffer, tip_position_buffer
383
+
384
+ def draw_arrow(self, end_coordinate: str, tip_coordinate: str) -> None:
385
+ """
386
+ Draws an arrow between two squares.
387
+
388
+ Parameters:
389
+ ----------
390
+ end_coordinate : str
391
+ The coordinate of the square where the arrow ends.
392
+ tip_coordinate : str
393
+ The coordinate of the square where the arrow starts.
394
+ """
395
+ ARROW_COLOR = ManimColor('#E09651')
396
+ end_square = self.squares[end_coordinate]
397
+ tip_square = self.squares[tip_coordinate]
398
+
399
+ end_position = end_square.get_center()
400
+ tip_position = tip_square.get_center()
401
+
402
+ end_position_buffer, tip_position_buffer = self.get_arrow_buffer(end_position, tip_position)
403
+
404
+ # Check if horizontal or vertical line or perfect diagonal like bishop
405
+ subtracted_position_vectors = end_position - tip_position
406
+ dir_x = subtracted_position_vectors[0]
407
+ dir_y = subtracted_position_vectors[1]
408
+ if dir_x == 0 or dir_y == 0 or abs(dir_x) == abs(dir_y):
409
+ arrow = Line(stroke_width=15, stroke_opacity=.8, fill_color=ARROW_COLOR, stroke_color=ARROW_COLOR)
410
+ arrow.reset_endpoints_based_on_tip = lambda *args: None
411
+ arrow.set_points_as_corners([end_position - end_position_buffer, tip_position - tip_position_buffer])
412
+ tip = arrow.create_tip()
413
+ tip.move_to(tip_position - tip_position_buffer)
414
+ finished_arrow = VGroup(arrow, tip)
415
+ self.add(finished_arrow)
416
+ else:
417
+ arrow0 = Line(stroke_width=15, stroke_opacity=.8, fill_color=ARROW_COLOR, stroke_color=ARROW_COLOR)
418
+ arrow0.reset_endpoints_based_on_tip = lambda *args: None
419
+ arrow1 = Line(stroke_width=15, stroke_opacity=.8, fill_color=ARROW_COLOR, stroke_color=ARROW_COLOR)
420
+ arrow1.reset_endpoints_based_on_tip = lambda *args: None
421
+
422
+ if dir_y > 0:
423
+ buffer_y = np.array([0, 0.25, 0])
424
+ else:
425
+ buffer_y = np.array([0, -0.25, 0])
426
+
427
+ if dir_x > 0:
428
+ buffer_x = np.array([0.25, 0, 0])
429
+ else:
430
+ buffer_x = np.array([-0.25, 0, 0])
431
+
432
+ tip_buffer = -0.07 if dir_x > 0 else 0.07
433
+
434
+ if abs(dir_y) > abs(dir_x):
435
+ arrow0.set_points_as_corners([end_position - buffer_y, np.array([end_position[0], tip_position[1], 0])])
436
+ arrow1.set_points_as_corners([np.array([end_position[0]-tip_buffer, tip_position[1], 0]), tip_position + buffer_x])
437
+ tip = arrow1.create_tip()
438
+ tip.move_to(tip_position + buffer_x)
439
+ else:
440
+ arrow0.set_points_as_corners([end_position - buffer_x, np.array([tip_position[0], end_position[1], 0])])
441
+ arrow1.set_points_as_corners([np.array([tip_position[0]-tip_buffer, end_position[1], 0]), tip_position + buffer_y])
442
+ tip = arrow1.create_tip()
443
+ tip.move_to(tip_position + buffer_y)
444
+
445
+ finished_arrow = VGroup(arrow0, arrow1, tip)
446
+ self.add(finished_arrow)
447
+ self.arrows.append(finished_arrow)
448
+
449
+ def remove_piece(self, coordinate: str) -> None:
450
+ """
451
+ Removes a piece from the board.
452
+
453
+ Parameters:
454
+ ----------
455
+ coordinate : str
456
+ The coordinate of the piece to be removed.
457
+ """
458
+ piece_to_remove = self.pieces[coordinate]
459
+ self.remove(piece_to_remove)
460
+ del piece_to_remove
461
+ del self.pieces[coordinate]
462
+
463
+ def remove_arrows(self) -> None:
464
+ """
465
+ Removes all arrows from the board.
466
+ """
467
+ for arrow in self.arrows:
468
+ self.remove(arrow)
469
+
470
+ def move_piece(self, starting_coordinate: str, ending_coordinate: str) -> None:
471
+ """
472
+ Moves a piece from one square to another.
473
+
474
+ Parameters:
475
+ ----------
476
+ starting_coordinate : str
477
+ The coordinate of the square where the piece is currently located.
478
+ ending_coordinate : str
479
+ The coordinate of the square where the piece is to be moved.
480
+ """
481
+ if ending_coordinate in self.pieces.keys():
482
+ self.remove_piece(ending_coordinate)
483
+ try:
484
+ piece_to_move = self.pieces[starting_coordinate]
485
+ self.pieces[ending_coordinate] = piece_to_move
486
+ del self.pieces[starting_coordinate]
487
+
488
+ for coordinate in self.highlighted_squares:
489
+ self.unmark_square(coordinate)
490
+ self.highlighted_squares = []
491
+
492
+ piece_to_move.move_to(self.squares[ending_coordinate].get_center())
493
+ self.highlight_square(starting_coordinate)
494
+ self.highlight_square(ending_coordinate)
495
+ self.highlighted_squares.append(starting_coordinate)
496
+ self.highlighted_squares.append(ending_coordinate)
497
+ except Exception as e:
498
+ print(f'{e} has no piece associated')
499
+
500
+ def promote_piece(self, coordinate: str, piece_type: str) -> None:
501
+ """
502
+ Promotes a piece to another piece type.
503
+
504
+ Parameters:
505
+ ----------
506
+ coordinate : str
507
+ The coordinate of the piece to be promoted.
508
+ piece_type : str
509
+ The type of piece to which the piece is promoted (e.g., 'Q' for Queen).
510
+ """
511
+ piece_color = self.pieces[coordinate].is_white
512
+ self.remove_piece(coordinate)
513
+ self.add_piece(piece_type.upper(), piece_color, coordinate)
514
+
515
+ def get_piece_at_square(self, coordinate: str):
516
+ """
517
+ Returns the piece at a given coordinate, if any.
518
+
519
+ Parameters:
520
+ ----------
521
+ coordinate : str
522
+ The coordinate of the square to check for a piece.
523
+
524
+ Returns:
525
+ -------
526
+ object or None
527
+ The piece object at the specified coordinate, or None if no piece is present.
528
+ """
529
+ if coordinate in self.pieces:
530
+ return self.pieces[coordinate]
531
+ else:
532
+ return None # No piece at the given coordinate
src/evaluation_bar.py ADDED
@@ -0,0 +1,84 @@
1
+ from manim import *
2
+ from typing import Tuple
3
+
4
+ class EvaluationBar(Mobject):
5
+ """
6
+ A class to represent an evaluation bar using Manim for visualization.
7
+
8
+ Attributes:
9
+ ----------
10
+ evaluation : float
11
+ The current evaluation value, default is 0.0.
12
+ BLACK : ManimColor
13
+ The color used for the black portion of the bar.
14
+ WHITE : ManimColor
15
+ The color used for the white portion of the bar.
16
+ black_rectangle : Rectangle
17
+ The rectangle representing the black portion of the evaluation bar.
18
+ white_rectangle : Rectangle
19
+ The rectangle representing the white portion of the evaluation bar.
20
+ bot_text : Text
21
+ The text object displaying the evaluation at the bottom of the bar.
22
+ top_text : Text
23
+ The text object displaying the evaluation at the top of the bar.
24
+ """
25
+
26
+ def __init__(self, evaluation=0.0) -> None:
27
+ """
28
+ Initializes the EvaluationBar object with default or specified evaluation.
29
+
30
+ Parameters:
31
+ ----------
32
+ evaluation : float, optional
33
+ The initial evaluation value (default is 0.0).
34
+ """
35
+ super().__init__()
36
+ self.evaluation = evaluation
37
+ self.BLACK = ManimColor("#403D39")
38
+ self.WHITE = ManimColor("#ffffff")
39
+ self.black_rectangle = Rectangle(width=0.25, height=6.4, stroke_color=self.BLACK, fill_opacity=1).set_fill(self.BLACK)
40
+ self.white_rectangle = Rectangle(width=0.25, height=3.2, stroke_color=self.WHITE, fill_opacity=1).set_fill(self.WHITE)
41
+ self.bot_text = Text('0.0', font="Arial").move_to(self.black_rectangle.get_bottom() + np.array([0, 0.2, 0])).set_fill(self.BLACK).scale(0.2)
42
+ self.top_text = Text('0.0', font="Arial").move_to(self.black_rectangle.get_top() + np.array([0, -0.2, 0])).set_fill(self.WHITE).scale(0.2)
43
+ self.__add_rectangles()
44
+
45
+ def set_evaluation(self, evaluation: float):
46
+ """
47
+ Updates the evaluation value and adjusts the visual representation accordingly.
48
+
49
+ Parameters:
50
+ ----------
51
+ evaluation : float
52
+ The new evaluation value to be set.
53
+
54
+ Returns:
55
+ -------
56
+ list
57
+ A list of Transform animations to update the evaluation bar.
58
+ """
59
+ self.evaluation = evaluation
60
+ text_transformation = None
61
+ if self.evaluation > 0:
62
+ text_transformation = Transform(
63
+ self.bot_text,
64
+ Text(f'{self.evaluation:.1f}', font="Arial").move_to(self.black_rectangle.get_bottom() + np.array([0, 0.2, 0])).set_fill(self.BLACK).scale(0.2)
65
+ )
66
+ else:
67
+ text_transformation = Transform(
68
+ self.bot_text,
69
+ Text(f'{self.evaluation:.1f}', font="Arial").move_to(self.black_rectangle.get_top() + np.array([0, -0.2, 0])).set_fill(self.WHITE).scale(0.2)
70
+ )
71
+
72
+ height_from_evaluation = 0.737063 * self.evaluation + 3.2
73
+ rect_height = min(max(0.32, height_from_evaluation), 6.18)
74
+ pos = self.black_rectangle.get_bottom() + np.array([0, rect_height / 2, 0])
75
+ new_rect = Rectangle(width=0.25, height=rect_height, stroke_color=self.WHITE, fill_opacity=1).set_fill(self.WHITE).move_to(pos)
76
+ return [Transform(self.white_rectangle, new_rect), text_transformation]
77
+
78
+ def __add_rectangles(self) -> None:
79
+ """
80
+ Adds the rectangles and text to the evaluation bar.
81
+ """
82
+ self.add(self.black_rectangle)
83
+ self.add(self.white_rectangle.shift(DOWN * 1.6))
84
+ self.add(self.bot_text)