manim-chess 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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)