3838import chess
3939import chess .svg
4040import numpy as np
41-
42- class AsciiTable :
43- """Helper class for creating perfectly aligned ASCII tables with dynamic column widths"""
44-
45- @staticmethod
46- def create (data , headers , alignments = None , padding = 1 , border_style = "none" , title = None ):
47- """
48- Create a perfectly aligned ASCII table with dynamic column widths
49-
50- Args:
51- data: List of rows (each row is a list of cells)
52- headers: List of header names
53- alignments: List of alignments ('<', '>', '^') for each column
54- padding: Number of spaces between columns
55- border_style: "none", "minimal", "full"
56- title: Optional table title
57-
58- Returns:
59- str: Formatted table
60- """
61- # Déterminer la largeur maximale de chaque colonne (incluant les en-têtes)
62- all_rows = [headers ] + data
63- col_widths = [
64- max (len (str (row [i ])) for row in all_rows if i < len (row ))
65-
66- for i in range (max (len (row ) for row in all_rows ))
67- ]
68-
69- # Appliquer le padding
70- col_widths = [w + padding * 2 for w in col_widths ]
71-
72- # Définir les alignements par défaut (gauche pour le texte, droite pour les nombres)
73-
74- if not alignments :
75- alignments = ['<' if i == 0 else '>' for i in range (len (col_widths ))]
76-
77- # Créer les lignes du tableau
78- lines = []
79-
80- # Ajouter un titre si spécifié
81-
82- if title :
83- lines .append (f"{ title } " )
84- lines .append ("─" * sum (col_widths + [len (col_widths ) - 1 ]))
85-
86- # Ligne de séparation supérieure si nécessaire
87-
88- if border_style == "full" :
89- top_border = '+' + '+' .join (['─' * w for w in col_widths ]) + '+'
90- lines .append (top_border )
91-
92- # En-têtes
93- header_line = ''
94-
95- if border_style in ["minimal" , "full" ]:
96- header_line += '|'
97-
98- for i , header in enumerate (headers ):
99- fmt = f"{{:{ alignments [i ]} { col_widths [i ]} }}"
100- header_line += fmt .format (header )
101-
102- if border_style in ["minimal" , "full" ] or i < len (headers ) - 1 :
103- header_line += '|'
104- lines .append (header_line )
105-
106- # Ligne de séparation après les en-têtes si nécessaire
107-
108- if border_style == "full" :
109- separator = '+' + '+' .join (['─' * w for w in col_widths ]) + '+'
110- lines .append (separator )
111- elif border_style == "minimal" :
112- separator = ':' + ':' .join (['-' * w for w in col_widths ]) + ':'
113- lines .append (separator )
114-
115- # Données
116-
117- for row in data :
118- row_line = ''
119-
120- if border_style in ["minimal" , "full" ]:
121- row_line += '|'
122-
123- for i , cell in enumerate (row ):
124- if i >= len (col_widths ):
125- continue
126- fmt = f"{{:{ alignments [i ]} { col_widths [i ]} }}"
127- row_line += fmt .format (str (cell ))
128-
129- if border_style in ["minimal" , "full" ] or i < len (row ) - 1 :
130- row_line += '|'
131- lines .append (row_line )
132-
133- # Ligne de fermeture si nécessaire
134-
135- if border_style == "full" :
136- bottom_border = '+' + '+' .join (['─' * w for w in col_widths ]) + '+'
137- lines .append (bottom_border )
138-
139- return "\n " .join (lines )
140-
141- @staticmethod
142- def create_meter (value , width = 20 , symbol_filled = '█' , symbol_empty = '░' , show_value = True ):
143- """
144- Create a visual progress meter
145-
146- Args:
147- value: Value between 0 and 1
148- width: Total width of the meter
149- symbol_filled: Symbol for filled portion
150- symbol_empty: Symbol for empty portion
151- show_value: Whether to show the percentage value
152-
153- Returns:
154- str: Formatted meter
155- """
156- level = int (value * width )
157- meter = symbol_filled * level + symbol_empty * (width - level )
158-
159- if show_value :
160- return f"[{ meter } ] { value * 100 :.0f} %"
161-
162- return f"[{ meter } ]"
163-
164- @staticmethod
165- def create_balance_meter (white , black , width = 20 ):
166- """
167- Create a visual balance meter for white/black distribution
168-
169- Args:
170- white: Count of white positions
171- black: Count of black positions
172- width: Total width of the meter
173-
174- Returns:
175- str: Formatted balance meter
176- """
177- total = white + black
178-
179- if total == 0 :
180- return "[ ] 0.0%"
181-
182- white_pct = white / total * 100
183-
184- white_width = int (white_pct * width / 100 )
185- black_width = width - white_width
186-
187- meter = "♔" * white_width + "♚" * black_width
188- balance = 1.0 - abs (0.5 - white / total ) * 2
189- status = "(Perfect)" if balance > 0.9 else ""
190-
191- return f"[{ meter } ] { balance * 100 :.1f} % { status } "
192-
41+ from ascii_table import AsciiTable
19342
19443class ChessOpeningAnalyzer :
19544 """Professional chess opening deck analyzer with advanced visualization"""
@@ -258,10 +107,25 @@ def _analyze_data(self):
258107 'critical_positions' : []
259108 },
260109 'color_breakdown' : {
261- 'white' : {'mainlines' : 0 , 'variants' : 0 , 'families' : Counter (), 'moves' : []},
262- 'black' : {'mainlines' : 0 , 'variants' : 0 , 'families' : Counter (), 'moves' : []}
110+ 'white' : {
111+ 'mainlines' : 0 ,
112+ 'variants' : 0 ,
113+ 'families' : Counter (),
114+ 'moves' : [],
115+ },
116+ 'black' : {
117+ 'mainlines' : 0 ,
118+ 'variants' : 0 ,
119+ 'families' : Counter (),
120+ 'moves' : [],
121+ }
263122 },
264- 'family_breakdown' : defaultdict (lambda : {'white' : 0 , 'black' : 0 , 'mainlines' : 0 , 'variants' : 0 }),
123+ 'family_breakdown' : defaultdict (lambda : {
124+ 'white' : 0 ,
125+ 'black' : 0 ,
126+ 'mainlines' : 0 ,
127+ 'variants' : 0 ,
128+ }),
265129 'move_patterns' : {
266130 'first_moves' : Counter (),
267131 'common_sequences' : Counter (),
@@ -346,7 +210,8 @@ def _determine_color(self, move, puzzle_id, themes, color_lookup):
346210 board = chess .Board (fen )
347211
348212 return 'white' if board .turn == chess .WHITE else 'black'
349- except :
213+ except (AttributeError , ValueError ) as e :
214+ print (f"Error determining color: { e } " )
350215 pass
351216
352217 # 4. Last resort: position in deck
@@ -394,7 +259,8 @@ def _determine_family(self, move, puzzle_id, themes, category, category_lookup):
394259
395260 if first_move == 'e2e4' and 'c7c5' in [m .uci () for m in board .legal_moves ]:
396261 return 'sicilian'
397- except :
262+ except (AttributeError , ValueError ) as e :
263+ print (f"Error analyzing FEN: { e } " )
398264 pass
399265
400266 return 'unknown'
@@ -409,7 +275,9 @@ def _analyze_depth(self, move):
409275 move_number = board .fullmove_number
410276
411277 return (move_number - 1 ) * 2 + (1 if board .turn == chess .WHITE else 0 )
412- except :
278+ except (AttributeError , ValueError ) as e :
279+ print (f"Error analyzing depth: { e } " )
280+
413281 return 0
414282
415283 return 0
@@ -553,10 +421,18 @@ def _generate_console_report(self, include_visuals):
553421 f" • Total positions: { total } " ,
554422 f" • Color distribution: White { white } ({ white / total * 100 :.1f} %) | Black { black } ({ black / total * 100 :.1f} %)" ,
555423 "" ,
556- " • Balance level: " + AsciiTable .create_meter (stats ['quality_metrics' ]['balance' ], 20 ),
557- " • Theoretical completeness: " + AsciiTable .create_meter (stats ['quality_metrics' ]['completeness' ], 20 ),
558- " • Opening diversity: " + AsciiTable .create_meter (stats ['quality_metrics' ]['diversity' ], 20 ),
559- " • Theoretical soundness: " + AsciiTable .create_meter (stats ['quality_metrics' ]['theoretical_soundness' ], 20 ),
424+ " • Balance level: " + AsciiTable .create_meter (
425+ stats ['quality_metrics' ]['balance' ],
426+ 20 ),
427+ " • Theoretical completeness: " + AsciiTable .create_meter (
428+ stats ['quality_metrics' ]['completeness' ],
429+ 20 ),
430+ " • Opening diversity: " + AsciiTable .create_meter (
431+ stats ['quality_metrics' ]['diversity' ],
432+ 20 ),
433+ " • Theoretical soundness: " + AsciiTable .create_meter (
434+ stats ['quality_metrics' ]['theoretical_soundness' ],
435+ 20 ),
560436 ""
561437 ])
562438
@@ -796,7 +672,9 @@ def _generate_key_positions_preview(self):
796672 if fen :
797673 board = chess .Board (fen )
798674 key_positions .append ((board , move ))
799- except :
675+ except (AttributeError , ValueError ) as e :
676+ print (f"Error processing move: { e } " )
677+
800678 continue
801679
802680 if not key_positions :
@@ -853,58 +731,86 @@ def _generate_personalized_recommendations(self):
853731 black = stats ['metadata' ]['color_balance' ]['black' ]
854732
855733 if white > black * 1.5 :
856- recommendations .append ("♔ Strengthen your black defenses - you're too focused on white openings" )
734+ recommendations .append (
735+ "♔ Strengthen your black defenses - you're too focused on white openings" ,
736+ )
857737 elif black > white * 1.5 :
858- recommendations .append ("♚ Strengthen your white openings - you lack practice as white" )
738+ recommendations .append (
739+ "♚ Strengthen your white openings - you lack practice as white" ,
740+ )
859741
860742 # Mainline-based recommendations
861743 mainlines = (stats ['color_breakdown' ]['white' ]['mainlines' ] +
862744 stats ['color_breakdown' ]['black' ]['mainlines' ])
863745
864746 if mainlines / total < 0.3 :
865- recommendations .append ("📚 Add more mainlines - your deck lacks theoretical foundations" )
747+ recommendations .append (
748+ "📚 Add more mainlines - your deck lacks theoretical foundations" ,
749+ )
866750 elif mainlines / total > 0.7 :
867- recommendations .append ("💡 Add more variants - your deck is too theoretical without practical alternatives" )
751+ recommendations .append (
752+ "💡 Add more variants - your deck is too theoretical without practical alternatives" ,
753+ )
868754
869755 # Diversity-based recommendations
870756
871757 if len (stats ['metadata' ]['family_coverage' ]) < 5 :
872- recommendations .append ("🌍 Broaden your repertoire - you're focusing too much on few opening families" )
758+ recommendations .append (
759+ "🌍 Broaden your repertoire - you're focusing too much on few opening families" ,
760+ )
873761 elif len (stats ['metadata' ]['family_coverage' ]) > 12 :
874- recommendations .append ("🎯 Good diversity! Now focus on mastering key families" )
762+ recommendations .append (
763+ "🎯 Good diversity! Now focus on mastering key families" ,
764+ )
875765
876766 # Depth-based recommendations
877767 avg_depth = sum (k * v for k ,v in stats ['metadata' ]['depth_distribution' ].items ()) / total
878768
879769 if avg_depth < 3 :
880- recommendations .append ("♟ Develop deeper positions - your deck remains too superficial" )
770+ recommendations .append (
771+ "♟ Develop deeper positions - your deck remains too superficial" ,
772+ )
881773 elif avg_depth > 10 :
882- recommendations .append ("🧠 Excellent! Your deck covers well developments beyond the first few moves" )
774+ recommendations .append (
775+ "🧠 Excellent! Your deck covers well developments beyond the first few moves" ,
776+ )
883777
884778 # Personalized recommendation based on first moves
885779
886780 if stats ['move_patterns' ]['first_moves' ]:
887781 top_move = stats ['move_patterns' ]['first_moves' ].most_common (1 )[0 ][0 ]
888782
889783 if top_move in ['e4' , 'd4' , 'Nf3' , 'c4' ]:
890- recommendations .append (f"🚀 You master { top_move } well - now explore less common responses" )
784+ recommendations .append (
785+ f"🚀 You master { top_move } well - now explore less common responses" ,
786+ )
891787 else :
892- recommendations .append (f"🔍 { top_move } is a good start - ensure you know main responses" )
788+ recommendations .append (
789+ f"🔍 { top_move } is a good start - ensure you know main responses" ,
790+ )
893791
894792 # If no specific recommendations
895793
896794 if not recommendations :
897- recommendations .append ("✅ Excellent deck! Keep practicing regularly" )
795+ recommendations .append (
796+ "✅ Excellent deck! Keep practicing regularly" ,
797+ )
898798
899799 # Add progression recommendation
900800 completeness = stats ['quality_metrics' ]['completeness' ]
901801
902802 if completeness < 0.3 :
903- recommendations .append ("🎯 Short-term goal: Reach 5 main opening families" )
803+ recommendations .append (
804+ "🎯 Short-term goal: Reach 5 main opening families" ,
805+ )
904806 elif completeness < 0.6 :
905- recommendations .append ("🏆 Intermediate goal: Master 8 key opening families" )
807+ recommendations .append (
808+ "🏆 Intermediate goal: Master 8 key opening families" ,
809+ )
906810 else :
907- recommendations .append ("🏅 Advanced goal: Explore specialized variants in your favorite families" )
811+ recommendations .append (
812+ "🏅 Advanced goal: Explore specialized variants in your favorite families" ,
813+ )
908814
909815 # Format recommendations with priorities
910816 formatted = []
0 commit comments